← back to AbramsOS

scripts/seed-rights-rules.js

117 lines

#!/usr/bin/env node
// Seed the rights_rule_snapshot table with a hand-curated starter corpus.
// All rules are public statutory/regulatory references. Real ingestion of
// the full CFR / FTC guidance / DOT rules is a future tick — this is enough
// for the claim strategist to cite SOMETHING accurate in drafts.
//
// Idempotent: ON CONFLICT (citation) WHERE superseded_at IS NULL DO UPDATE.

require('dotenv').config();
const db = require('../lib/db');
const { id } = require('../lib/ids');

const RULES = [
  {
    domain: 'card_dispute', jurisdiction: 'US',
    citation: '15 USC §1666i / 12 CFR §1026.12(c)',
    source_url: 'https://www.consumerfinance.gov/rules-policy/regulations/1026/12/',
    short_title: 'Credit-card claims-and-defenses (FCBA / Reg Z)',
    rule_summary: 'A consumer using a credit card may assert against the card issuer all defenses (other than tort) that they could assert against the merchant, when the purchase was over $50 and either in the same state or within 100 miles of the cardholder\'s billing address. The right is in addition to the merchant chargeback process.',
    citation_block: 'Under the federal Fair Credit Billing Act (15 U.S.C. §1666i) and Regulation Z (12 C.F.R. §1026.12(c)), I am asserting my claims-and-defenses rights against my card issuer for the disputed charge described above.',
    applies_when: { reason_codes: ['returns_window_closing', 'merchant_no_response'], routing: ['issuer'], min_amount: 50 },
  },
  {
    domain: 'card_dispute', jurisdiction: 'US',
    citation: '15 USC §1666 / 12 CFR §1026.13',
    source_url: 'https://www.consumerfinance.gov/rules-policy/regulations/1026/13/',
    short_title: 'Billing-error rights (60-day written notice)',
    rule_summary: 'A consumer must send a written billing-error notice within 60 days of the first statement showing the disputed charge. The issuer must acknowledge within 30 days and resolve within two billing cycles (max 90 days).',
    citation_block: 'Pursuant to 12 C.F.R. §1026.13, I am providing written notice of a billing error within the required 60-day window. I request acknowledgment within 30 days and resolution within two billing cycles.',
    applies_when: { reason_codes: ['returns_window_closing'], routing: ['issuer'] },
  },
  {
    domain: 'warranty', jurisdiction: 'US',
    citation: 'Magnuson-Moss Warranty Act / 15 USC §2301-2312',
    source_url: 'https://www.ftc.gov/legal-library/browse/statutes/magnuson-moss-warranty-federal-trade-commission-improvement-act',
    short_title: 'Magnuson-Moss federal warranty law',
    rule_summary: 'Written warranties on consumer products costing over $15 must be conspicuously labeled "full" or "limited", and the warrantor must remedy defects within a reasonable time at no charge. After a reasonable number of attempts, the consumer may demand refund or replacement.',
    citation_block: 'Under the Magnuson-Moss Warranty Act (15 U.S.C. §§2301-2312), the warrantor is obligated to remedy defects within a reasonable time at no charge. After repeated unsuccessful attempts, I am entitled to refund or replacement.',
    applies_when: { reason_codes: ['warranty_expiry'], routing: ['manufacturer'] },
  },
  {
    domain: 'warranty', jurisdiction: 'US',
    citation: '16 CFR §700 (FTC warranty advertising guides)',
    source_url: 'https://www.ftc.gov/legal-library/browse/rules/disclosure-written-consumer-product-warranty-terms-conditions',
    short_title: 'FTC pre-sale warranty disclosure rule',
    rule_summary: 'Sellers of consumer products with written warranties must make warranty terms available for the consumer to review BEFORE the sale. Vague claims like "limited lifetime warranty" must clearly state covered components, duration, and remedy.',
    citation_block: 'Per 16 C.F.R. §700, the seller is required to make warranty terms available for pre-sale review. The advertised warranty terms govern, and any ambiguity is construed against the warrantor.',
    applies_when: { reason_codes: ['warranty_expiry'], routing: ['merchant', 'manufacturer'] },
  },
  {
    domain: 'returns', jurisdiction: 'US',
    citation: 'FTC Money-Back Guarantee guides',
    source_url: 'https://www.ftc.gov/business-guidance/resources/advertising-faqs-guide-small-business',
    short_title: 'FTC truth-in-advertising / money-back guarantee',
    rule_summary: 'When a seller advertises a "money-back guarantee", "satisfaction guaranteed", or similar promise, the FTC requires the seller to honor the promise on the consumer\'s request without imposing material conditions not disclosed in the offer.',
    citation_block: 'Per the FTC\'s guides on advertising and money-back guarantees, the merchant\'s public guarantee must be honored on request without undisclosed conditions.',
    applies_when: { reason_codes: ['returns_window_closing'], routing: ['merchant'] },
  },
  {
    domain: 'airline', jurisdiction: 'US',
    citation: '14 CFR §259.5 / DOT refund rule',
    source_url: 'https://www.transportation.gov/airconsumer/refunds',
    short_title: 'DOT — airline refunds for cancelled / significantly changed flights',
    rule_summary: 'A US-flagged carrier or any carrier serving the US must give a prompt cash refund (not just a voucher) when the airline cancels a flight or makes a significant schedule change and the passenger declines the alternative. Codified by the DOT 2024 final rule.',
    citation_block: 'Under U.S. Department of Transportation 14 C.F.R. §259.5 and the 2024 refund rule, I am entitled to a prompt cash refund — not a voucher — for the cancelled/significantly-changed flight described above.',
    applies_when: { reason_codes: ['airline_cancellation', 'airline_significant_change'], routing: ['merchant'] },
  },
  {
    domain: 'airline', jurisdiction: 'EU',
    citation: 'Regulation (EC) 261/2004',
    source_url: 'https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32004R0261',
    short_title: 'EU 261 — standard compensation for delays/cancellations',
    rule_summary: 'For flights departing from an EU airport (or arriving in EU on an EU carrier), passengers are entitled to fixed compensation (€250 / €400 / €600 by distance) when delayed ≥3 hours, cancelled within 14 days, or denied boarding, unless the carrier proves extraordinary circumstances.',
    citation_block: 'Under Regulation (EC) 261/2004, I am entitled to standard compensation of €[amount] for the qualifying delay/cancellation/denied-boarding event described above. The carrier bears the burden of proving extraordinary circumstances.',
    applies_when: { reason_codes: ['airline_cancellation', 'airline_significant_change'], routing: ['merchant'], jurisdiction_match: 'EU' },
  },
  {
    domain: 'recall', jurisdiction: 'US',
    citation: 'CPSC Recall — manufacturer remedy obligation',
    source_url: 'https://www.cpsc.gov/Recalls',
    short_title: 'CPSC recall remedy entitlement',
    rule_summary: 'For a CPSC-published recall, the firm has agreed with the U.S. Consumer Product Safety Commission to provide a specific remedy (refund, repair, or replacement) at no cost to consumers. The remedy is enforceable under the firm\'s recall agreement.',
    citation_block: 'Per the CPSC recall notice [ID] published on [date], I am entitled to the agreed remedy ([refund/repair/replacement]) at no cost to me.',
    applies_when: { reason_codes: ['recall_action_due'], routing: ['manufacturer'] },
  },
];

async function main() {
  let inserted = 0, updated = 0;
  for (const r of RULES) {
    const result = await db.query(
      `INSERT INTO rights_rule_snapshot
         (id, domain, jurisdiction, citation, source_url, short_title, rule_summary, citation_block, applies_when)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
       ON CONFLICT (citation) WHERE superseded_at IS NULL DO UPDATE SET
         domain = EXCLUDED.domain,
         jurisdiction = EXCLUDED.jurisdiction,
         source_url = EXCLUDED.source_url,
         short_title = EXCLUDED.short_title,
         rule_summary = EXCLUDED.rule_summary,
         citation_block = EXCLUDED.citation_block,
         applies_when = EXCLUDED.applies_when
       RETURNING id, (xmax = 0) AS was_inserted`,
      [id('document'), r.domain, r.jurisdiction, r.citation, r.source_url, r.short_title, r.rule_summary, r.citation_block, JSON.stringify(r.applies_when)]
    );
    if (result.rows[0].was_inserted) inserted += 1; else updated += 1;
  }
  console.log(`[seed-rights-rules] inserted=${inserted} updated=${updated} total=${RULES.length}`);
  await db.pool.end();
}

if (require.main === module) {
  main().catch((e) => { console.error(e); process.exit(1); });
}

module.exports = { RULES };