← back to Atomic50 Onboard
repair-descriptions.js
129 lines
#!/usr/bin/env node
/**
* Atomic 50 Ceilings — data repair (REVERSIBLE, staging table only)
*
* 1. product_type: reframe tin tiles + moldings from the wrong "Commercial
* Wallcovering" to DW canonical "Architectural Wallcoverings" (the value the
* architectural-update-skill migrates all contract/panel lines to; 100 such
* products already live). Accessories/Tools keep their install-hardware type.
* 2. ai_description: rewrite each product to accurately describe a pressed-tin
* ceiling tile (T1 tin-plated steel, 25% recycled, unfinished tin + 40+
* powder-coat colors) with its REAL motif (from ai_patterns/ai_styles —
* Victorian/Damask/Geometric/Art Deco/Floral are accurate to embossed tin)
* and applications (ceiling, wall panel, backsplash, wainscot). Word
* "wallpaper" is BANNED; tin tiles are NOT "wallcoverings" in copy — they are
* pressed-tin ceiling/wall tiles.
* 3. prices left NULL (quote-only).
*
* $0 (local) — deterministic template compose from DB fields, NO paid API/LLM.
* A full snapshot is taken into atomic50_catalog_bak_repair before any write so
* the change is trivially reversible.
*/
const { Client } = require('pg');
const DRY = process.argv.includes('--dry-run');
// interior-designer lexicon → tin-accurate phrasing
const MATERIAL =
'pressed from T1-grade tin-plated steel (25% recycled content) and finished in raw, unfinished tin — with 40+ powder-coat colors available to order';
const APPLICATIONS_TILE =
'Installs as a pressed-tin ceiling field, an accent wall or wainscot panel, or a backsplash. American-made, ASTM E84 Class A rated.';
const APPLICATIONS_MOLDING =
'A matching pressed-tin trim that finishes the perimeter of a tin ceiling or wall-panel installation. American-made, ASTM E84 Class A rated.';
// motif phrasing keyed off the real ai_patterns / ai_styles values
function motifPhrase(patterns, styles) {
const p = (patterns || []).map((s) => String(s).toLowerCase());
const st = (styles || []).map((s) => String(s).toLowerCase());
const has = (arr, ...ks) => ks.some((k) => arr.some((v) => v.includes(k)));
const bits = [];
if (has(p, 'damask')) bits.push('a classic damask relief with ornate scroll and medallion motifs');
else if (has(p, 'floral') && has(p, 'geometric')) bits.push('an embossed pattern of floral motifs set within a geometric grid');
else if (has(p, 'floral')) bits.push('a repeating floral relief in the Victorian tradition');
else if (has(p, 'geometric')) bits.push('a crisp geometric relief of nested squares and diamonds');
else if (has(p, 'textured')) bits.push('a hammered, deeply textured surface');
else bits.push('a decorative embossed relief');
let styleWord = '';
if (has(st, 'art deco')) styleWord = 'with a strong Art Deco geometry';
else if (has(st, 'victorian')) styleWord = 'in an ornate Victorian idiom';
else if (has(st, 'rococo')) styleWord = 'with Rococo flourish';
else if (has(st, 'traditional')) styleWord = 'in a traditional register';
else if (has(st, 'industrial')) styleWord = 'with an industrial edge';
return styleWord ? `${bits[0]}, ${styleWord}` : bits[0];
}
function tileDescription(row) {
const motif = motifPhrase(row.ai_patterns, row.ai_styles);
return `Pressed-tin ceiling tile stamped with ${motif}. Each tile is ${MATERIAL}. The raw tin catches light with a soft metallic sheen and can be left unfinished or powder-coated to a custom color. ${APPLICATIONS_TILE}`;
}
function moldingDescription(row, kind) {
const motif = motifPhrase(row.ai_patterns, row.ai_styles);
return `Pressed-tin ${kind.toLowerCase()} stamped with ${motif}. It is ${MATERIAL}. ${APPLICATIONS_MOLDING}`;
}
function accessoryDescription(row) {
// Strip the banned "wallcovering" framing; accessories are install hardware.
const name = row.pattern_name || 'Install accessory';
return `${name} — an installation component for the Atomic 50 pressed-tin ceiling and wall-panel system. Made from durable metal/steel hardware sized to the Atomic 50 tin tile line.`;
}
(async () => {
const c = new Client({ host: '/tmp', database: 'dw_unified' });
await c.connect();
const { rows } = await c.query(
`SELECT id, product_type, pattern_name, ai_patterns, ai_styles FROM atomic50_catalog ORDER BY id`
);
// reversible snapshot
if (!DRY) {
await c.query(`DROP TABLE IF EXISTS atomic50_catalog_bak_repair`);
await c.query(`CREATE TABLE atomic50_catalog_bak_repair AS SELECT * FROM atomic50_catalog`);
console.log('snapshot -> atomic50_catalog_bak_repair');
}
let ptChanged = 0, descChanged = 0;
for (const r of rows) {
let newType = r.product_type;
let newDesc;
if (r.product_type === 'Commercial Wallcovering') {
newType = 'Architectural Wallcoverings';
newDesc = tileDescription(r);
} else if (/Molding/i.test(r.product_type)) {
newType = 'Architectural Wallcoverings';
newDesc = moldingDescription(r, r.product_type);
} else {
// Accessory / Tool — keep type, but scrub any leaked wallcovering framing
newDesc = accessoryDescription(r);
}
const willTypeChange = newType !== r.product_type;
if (willTypeChange) ptChanged++;
descChanged++;
if (DRY) {
if (r.id <= 3 || willTypeChange && ptChanged <= 3)
console.log(`#${r.id} [${r.product_type} -> ${newType}] ${newDesc.slice(0, 140)}...`);
continue;
}
await c.query(
`UPDATE atomic50_catalog
SET product_type=$1, ai_description=$2, updated_at=now()
WHERE id=$3`,
[newType, newDesc, r.id]
);
}
console.log(`${DRY ? '[DRY] ' : ''}rows=${rows.length} product_type_changed=${ptChanged} descriptions_rewritten=${descChanged}`);
await c.end();
})().catch((e) => {
console.error(e);
process.exit(1);
});