← back to AbramsOS

lib/reviews/drafts.js

162 lines

'use strict';
/**
 * Draft generator — Steve's genuine positive voice, $0 local. Ported from the
 * AbramsEgo spend-reviews module (TK-11433) into AbramsOS.
 *
 * VARIETY is a safety requirement: batch-approving near-identical 5-star reviews
 * gets Google/Amazon accounts penalized. Each draft rotates OPENER, STRUCTURE,
 * LENGTH, and RATING LANGUAGE — seeded deterministically by the item id (stable
 * per item so the content-seal holds across re-generation, varied across items).
 * No LLM, no network.
 */

const SLOP_SWAPS = [
  [/\belevate(s|d)?\b/gi, (m) => m.endsWith('s') ? 'improves' : m.endsWith('d') ? 'improved' : 'improve'],
  [/\bunlock(s|ed)?\b/gi, 'made possible'],
  [/\bseamless(ly)?\b/gi, (m) => /ly$/i.test(m) ? 'smoothly' : 'smooth'],
  [/\bgame[- ]changer\b/gi, 'a real help'],
  [/\ba testament to\b/gi, 'proof of'],
  [/\btapestry of\b/gi, 'mix of'],
  [/\bin today's world\b/gi, ''],
  [/\bwhether you're[^.,;]*\bor\b[^.,;]*/gi, ''],
  [/\bnot only\b([^,]*),? but also\b/gi, '$1 and'],
  [/\bboast(s|ed|ing)?\b/gi, 'has'],
  [/\btruly\b/gi, ''],
  [/\bstunning(ly)?\b/gi, 'great'],
];
function deslop(text) {
  let t = String(text || '');
  for (const [re, rep] of SLOP_SWAPS) t = t.replace(re, rep);
  t = t.replace(/\s*—\s*/g, ', ');
  t = t.replace(/[ \t]{2,}/g, ' ').replace(/\s+([.,;!?])/g, '$1');
  t = t.replace(/\s{2,}/g, ' ').replace(/(^|\. )([a-z])/g, (m, a, b) => a + b.toUpperCase());
  return t.trim();
}

function hash(s) { let h = 0; for (const c of String(s)) h = (h * 31 + c.charCodeAt(0)) | 0; return h; }
function pick(arr, seed) { return arr[Math.abs(hash(seed)) % arr.length]; }

// Amazon titles are 150-200 chars of keyword soup. A human says "the Maldon sea
// salt", not the whole SKU. Trim to a short, natural name.
function shortProduct(p) {
  if (!p) return '';
  let s = String(p).split(/[,(|;]/)[0].trim();
  s = s.replace(/\s*[-–—]\s*/g, ' ').replace(/\s{2,}/g, ' ').trim();
  const words = s.split(/\s+/);
  if (words.length > 7) s = words.slice(0, 7).join(' ');
  if (s.length > 48) s = s.slice(0, 48).replace(/\s+\S*$/, '').trim();
  return s || String(p).slice(0, 40).trim();
}

const OPEN = [
  'Really happy with this one.', 'Genuinely pleased with the whole experience.',
  'This worked out exactly the way I hoped.', 'Glad I went with them.',
  'No complaints at all here.', 'Exactly what I was looking for.',
  'Honestly exceeded what I expected.', 'A good find that I keep coming back to.',
  'Well worth it in the end.', 'Couldn\'t have asked for better.',
];
const MID_PRODUCT = [
  'The {product} is well made and does exactly what I needed.',
  'The {product} arrived in great shape and has held up well.',
  'Quality on the {product} is better than I expected for the price.',
  'The {product} has been solid so far and I use it often.',
  'The {product} matched the description and feels built to last.',
  'I\'ve put the {product} through real use and it hasn\'t let me down.',
];
const MID_MERCH = [
  'Ordering from {merchant} was easy and everything showed up on time.',
  '{merchant} made the whole thing painless from checkout to delivery.',
  'Service from {merchant} was quick and straightforward.',
  '{merchant} clearly cares about getting the details right.',
  '{merchant} kept me posted the whole way and delivered as promised.',
  'The people at {merchant} were helpful and made it simple.',
];
const DETAIL = [
  'Packaging was tidy and nothing was damaged.',
  'Shipping was faster than I expected.',
  'Communication was clear from order to arrival.',
  'It was priced fairly for what you get.',
  'Setup was simple and the instructions were clear.',
  'Everything was exactly as pictured.',
];
const CLOSE = [
  'Would order again without hesitation.', 'Recommend them to anyone on the fence.',
  'Will be back for more.', 'Easy recommendation from me.',
  'I\'d happily buy from them again.', 'Solid experience start to finish.',
];
const RATING_LINE = [
  'Five stars from me.', '★★★★★', '5/5, no notes.', 'Easily five stars.',
  'Top marks.', 'A well-earned five.', 'Rating it a full five.', 'Five out of five.',
];

function fill(t, item) {
  const merch = item.display_merchant || item.merchant || 'them';
  return t.replace(/\{product\}/g, shortProduct(item.product) || 'item').replace(/\{merchant\}/g, merch);
}

function shapeGoogle(item) {
  const s = Math.abs(hash(item.id + 'shape')) % 4;
  const P = item.product;
  let parts;
  if (s === 0) parts = [pick(OPEN, item.id + 'g0'), fill(pick(MID_MERCH, item.id + 'gm'), item), pick(CLOSE, item.id + 'gc')];
  else if (s === 1) parts = [fill(pick(MID_MERCH, item.id + 'g1'), item), pick(DETAIL, item.id + 'gd'), pick(CLOSE, item.id + 'gc')];
  else if (s === 2) parts = [pick(OPEN, item.id + 'g2'), pick(CLOSE, item.id + 'gc')];
  else parts = [pick(OPEN, item.id + 'g3'), fill(pick(MID_MERCH, item.id + 'gm'), item), P ? fill(pick(MID_PRODUCT, item.id + 'gp'), item) : pick(DETAIL, item.id + 'gd'), pick(CLOSE, item.id + 'gc')];
  if (Math.abs(hash(item.id + 'grate')) % 3 !== 0) parts.push(pick(RATING_LINE, item.id + 'gr'));
  return parts;
}
function genGoogleReview(item) {
  return { title: null, text: deslop(shapeGoogle(item).join(' ')), rating: 5 };
}

function genAmazonReview(item) {
  const s = Math.abs(hash(item.id + 'ashape')) % 3;
  const prod = shortProduct(item.product) || 'this item';
  const specifics = [];
  if (item.product) specifics.push(`The ${prod} is exactly as described`);
  specifics.push('it arrived quickly and well packed');
  if (item.seller) specifics.push(`shipped by ${item.seller}`);
  const spec = specifics.length ? deslop(specifics.join(', ') + '.') : '';
  let parts;
  if (s === 0) parts = [pick(OPEN, item.id + 'a0'), fill(pick(MID_PRODUCT, item.id + 'ap'), item), spec];
  else if (s === 1) parts = [spec || fill(pick(MID_PRODUCT, item.id + 'a1'), item), pick(DETAIL, item.id + 'ad'), pick(CLOSE, item.id + 'ac')];
  else parts = [pick(OPEN, item.id + 'a2'), spec, fill(pick(MID_PRODUCT, item.id + 'ap'), item), pick(CLOSE, item.id + 'ac')];
  if (Math.abs(hash(item.id + 'arate')) % 3 !== 0) parts.push(pick(RATING_LINE, item.id + 'ar'));
  const titleBank = item.product ? [`Great ${prod}`, `${prod} — exactly as described`, `Very happy with the ${prod}`, `${prod} does the job well`] : ['Happy with this purchase', 'Would buy again', 'Solid purchase'];
  const title = deslop(pick(titleBank, item.id + 'at'));
  return { title, text: deslop(parts.filter(Boolean).join(' ')), rating: 5 };
}

function genSellerEmail(item) {
  const who = item.seller || item.display_merchant || item.merchant || 'there';
  const thing = shortProduct(item.product) || 'the product';
  const subjBank = [`Thank you — ${thing}`, `Really pleased with the ${thing}`, `A quick thank-you for the ${thing}`, `Great experience with the ${thing}`];
  const subject = deslop(pick(subjBank, item.id + 'st'));
  const openBank = [
    `I recently bought ${thing}${item.order_id ? ` (order ${item.order_id})` : ''} and wanted to say thank you. It has been great, and the quality and service both stood out.`,
    `Just wanted to reach out about the ${thing}${item.order_id ? ` (order ${item.order_id})` : ''} — it has worked out really well and I appreciated how smooth the whole process was.`,
    `Thank you for the ${thing}${item.order_id ? ` (order ${item.order_id})` : ''}. It arrived in great shape and has been exactly what I needed.`,
  ];
  const body = deslop([
    `Hi ${who},`, '', pick(openBank, item.id + 'sb'), '',
    `I left a positive review to help other buyers find you. Keep up the good work — I'll be back.`,
    '', 'Best,', 'Steve Abrams',
  ].join('\n'));
  return { title: subject, text: body, rating: null };
}

// item: { id, product, merchant, display_merchant, seller, order_id, isAmazon, merchant_domain }
// Returns { target: {title,text,rating} } — amazon+seller for Amazon; google only for non-Amazon w/ domain.
function generateForItem(item) {
  const out = {};
  if (item.isAmazon) {
    out.amazon_review = genAmazonReview(item);
    out.seller_email = genSellerEmail(item);
  } else if (item.merchant && item.merchant_domain) {
    out.google_review = genGoogleReview(item);
  }
  return out;
}

module.exports = { deslop, shortProduct, genGoogleReview, genAmazonReview, genSellerEmail, generateForItem };