{"record":{"id":"1bf435623d38b6c0","repo":"koala73/worldmonitor","slug":"duplicate-faq-question-question","errorCode":null,"errorMessage":" duplicate FAQ question: ' + question","messagePattern":" duplicate FAQ question: ' \\+ question","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/build-comparison-pages.mjs","lineNumber":457,"sourceCode":"      ['What does travel risk intelligence cost?', 'World Monitor is free without signup, with Pro from $39.99/month. Assistance providers such as International SOS and Crisis24 negotiate enterprise retainers with undisclosed list pricing.'],\n    ],\n  },\n];\n\nfunction applyComparisonNarrative(page) {\n  const narrative = COMPARISON_NARRATIVES[page.slug];\n  if (!narrative) {\n    throw new Error('Missing comparison narrative for ' + page.slug);\n  }\n  const faqs = [...page.faqs, ...(narrative.extraFaqs ?? [])];\n  if (faqs.length < 8 || faqs.length > 12) {\n    throw new Error(page.slug + ' FAQ count must be 8-12, got ' + faqs.length);\n  }\n  const faqNames = new Set();\n  for (const [question] of faqs) {\n    const key = String(question).trim().toLowerCase();\n    if (faqNames.has(key)) {\n      throw new Error(page.slug + ' duplicate FAQ question: ' + question);\n    }\n    faqNames.add(key);\n  }\n  const merged = { ...page, ...narrative, faqs };\n  delete merged.extraFaqs;\n  const whyBody = (merged.whyWeWinBody ?? []).join(' ').replace(/\\s+/g, ' ').trim();\n  if (!whyBody) {\n    throw new Error(page.slug + ' is missing whyWeWinBody');\n  }\n  if (whyBody === merged.whyWeWin.replace(/\\s+/g, ' ').trim()) {\n    throw new Error(page.slug + ' whyWeWinBody must not repeat whyWeWin');\n  }\n  if (merged.heading && !(merged.headingProse?.length || merged.competitorProfiles?.length)) {\n    throw new Error(page.slug + ' H2 \"' + merged.heading + '\" has no following prose');\n  }\n  if (!merged.evaluationHeading || !merged.evaluationProse?.length) {\n    throw new Error(page.slug + ' is missing the evaluation section');\n  }","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/koala73/worldmonitor/blob/7d06c8633d256c18e38133030bc3613976a96ec9/scripts/build-comparison-pages.mjs#L439-L475","documentation":"applyComparisonNarrative normalizes each FAQ question (trimmed, lowercased) into a Set and throws if the same question appears twice within a page's merged FAQ list. Duplicate questions would produce duplicate FAQPage JSON-LD entries and hurt SEO.","triggerScenarios":"The merged FAQ list for a slug (page.faqs plus narrative.extraFaqs) contains two questions that are identical after trim/lowercase, e.g. 'Is it free?' and '  is it free?  '.","commonSituations":"Adding an extraFaq that restates a question already on the base page; copy-pasting a FAQ into two narrative sections; near-identical wording that differs only by case or whitespace.","solutions":["Find the duplicated question named in the error and remove or reword one instance.","Prefer removing duplicates from extraFaqs rather than the base page faqs.","Reword one question so the trimmed/lowercase forms differ while keeping 8-12 total.","Add a local check that dedupes/casefolds questions before building the narrative."],"exampleFix":"// before\nfaqs: [['Is it free?', 'Yes']], extraFaqs: [['  is it free?  ', 'Absolutely']]\n// after\nfaqs: [['Is it free?', 'Yes']], extraFaqs: [['What does the free tier include?', 'Map layers and alerts.']]","handlingStrategy":"validation","validationCode":"const seen = new Set();\nfor (const [q] of [...page.faqs, ...(narrative.extraFaqs ?? [])]) {\n  const k = String(q).trim().toLowerCase();\n  if (seen.has(k)) throw new Error(`duplicate FAQ: ${q}`);\n  seen.add(k);\n}","typeGuard":"const faqsUnique = (faqs) => new Set(faqs.map(([q]) => String(q).trim().toLowerCase())).size === faqs.length;","tryCatchPattern":"try {\n  page = applyComparisonNarrative(page);\n} catch (e) {\n  if (String(e.message).includes('duplicate FAQ question')) {\n    console.error('Dedupe/reword this FAQ:', e.message);\n  } else throw e;\n}","preventionTips":["Run a dedupe check before merging extraFaqs into page.faqs.","Compare questions case-insensitively and trimmed, matching the build's normalization.","Keep a single canonical wording per question across base page and extras."],"tags":["build-time","content-validation","duplicates","faq"],"backgroundTag":"schema-validation-failed","analyzedSha":"7d06c8633d256c18e38133030bc3613976a96ec9","analyzedAt":"2026-09-15T16:44:39.439Z","contentChangedAt":"2026-09-15T16:44:39.439Z","schemaVersion":2},"datasetVersion":"2026-09-15T18:17:12.389Z"}