← back to Professional Directory
scripts/cache-healthcare-templates.js
90 lines
#!/usr/bin/env node
/**
* Cache top-rated healthcare-practice site bodies as structural templates
* for mockup generation. Adapted from lawyer-directory-builder/public/
* mockup-templates/top12.json + firm-*.body.txt pattern.
*
* Run once (or weekly):
* node scripts/cache-healthcare-templates.js
*
* Outputs:
* data/site-templates/top10.json — manifest [{id,name,site,score,body_file}]
* data/site-templates/<id>.body.txt — 6KB structural HTML excerpt per template
*
* The body files are scripts/styles-stripped HTML excerpts intended as
* "section ordering + layout density" inspiration for the LLM. The LLM is
* instructed to ignore the template's colors/fonts and apply our aesthetic.
*/
const fs = require('fs');
const path = require('path');
const cheerio = require('cheerio');
const { fetch } = require('undici');
const OUT = path.resolve(__dirname, '../data/site-templates');
fs.mkdirSync(OUT, { recursive: true });
// Curated top-rated healthcare practice landing pages — mix of
// big-system marketing pages, modern primary-care brands, and DTC telehealth.
// Score is a hand-assigned proxy for SEO+UX quality (90-100).
const TEMPLATES = [
{ id: 'mayo', name: 'Mayo Clinic', site: 'https://www.mayoclinic.org/', score: 100 },
{ id: 'cleveland', name: 'Cleveland Clinic', site: 'https://my.clevelandclinic.org/', score: 100 },
{ id: 'onemedical', name: 'One Medical', site: 'https://www.onemedical.com/', score: 98 },
{ id: 'forward', name: 'Forward Health', site: 'https://goforward.com/', score: 95 },
{ id: 'carbon', name: 'Carbon Health', site: 'https://carbonhealth.com/', score: 95 },
{ id: 'parsley', name: 'Parsley Health', site: 'https://www.parsleyhealth.com/', score: 95 },
{ id: 'tia', name: 'Tia', site: 'https://www.asktia.com/', score: 95 },
{ id: 'oak', name: 'Oak Street Health', site: 'https://www.oakstreethealth.com/', score: 92 },
{ id: 'galileo', name: 'Galileo Health', site: 'https://galileo.io/', score: 90 },
{ id: 'hims', name: 'Hims', site: 'https://www.hims.com/', score: 90 },
];
const UA = 'pd-template-cache/0.1 (research; contact: steve@designerwallcoverings.com)';
const MAX_BODY = 6500; // chars — gemma3:12b context allows more, this is the LLM-input cap
function stripToStructure(html) {
const $ = cheerio.load(html);
$('script, style, noscript, link, meta, head, iframe, svg').remove();
// Drop attributes that are noise for structural inspiration.
$('*').each((_, el) => {
if (el.type !== 'tag') return;
const keep = ['class', 'href', 'role'];
Object.keys(el.attribs || {}).forEach(k => { if (!keep.includes(k)) delete el.attribs[k]; });
});
// Cap class strings to first 60 chars to keep file size sane.
$('[class]').each((_, el) => {
el.attribs.class = String(el.attribs.class || '').slice(0, 60);
});
const body = $('body').html() || $.html();
return body
.replace(/<!--[\s\S]*?-->/g, '')
.replace(/\s+/g, ' ')
.replace(/>\s+</g, '><')
.trim()
.slice(0, MAX_BODY);
}
async function main() {
const manifest = [];
for (const t of TEMPLATES) {
process.stdout.write(`[tpl] ${t.id.padEnd(12)} ${t.site} ... `);
try {
const r = await fetch(t.site, { headers: { 'User-Agent': UA, Accept: 'text/html' }, signal: AbortSignal.timeout(15_000), redirect: 'follow' });
if (!r.ok) { console.log(`HTTP ${r.status}`); continue; }
const html = await r.text();
const body = stripToStructure(html);
if (body.length < 500) { console.log('body too small, skip'); continue; }
const file = `${t.id}.body.txt`;
fs.writeFileSync(path.join(OUT, file), body);
manifest.push({ id: t.id, name: t.name, site: t.site, score: t.score, body_file: file, body_size: body.length });
console.log(`${body.length}B`);
} catch (e) {
console.log(`ERR ${e.message}`);
}
}
fs.writeFileSync(path.join(OUT, 'top10.json'), JSON.stringify(manifest, null, 2));
console.log(`\n[tpl] cached ${manifest.length}/${TEMPLATES.length} → ${OUT}/top10.json`);
}
main().catch(e => { console.error(e); process.exit(1); });