Files
Omair Saleh 056c47581f feat: editorial review dashboard + elite-grade pilot batch (5 SKUs)
Ships the second dashboard surface — a Pattern Library + Preview Theatre — that
presents the 4-section PDP pilot batch back to Umar, compliance, and the board
in an editorial format. Adds the full data layer that drives it: 5 source-backed
per-SKU drafts at QA 100/100, 15 competitor PDP semantic extracts, PubMed
evidence packs, EFSA claims library extension, JV brand voice guide, hand-curated
product FAQs, and the Matrixify-ready CSV exports for Lewis.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 18:50:09 +08:00

59 lines
2.5 KiB
TypeScript

import { join } from 'path'
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs'
export const ROOT = join(import.meta.dir, '..', '..')
export const INTEL_ROOT = join(ROOT, 'data', 'intelligence')
export const SOURCES_ROOT = join(ROOT, 'data', 'sources')
export const CONTENT_PRODUCTS_ROOT = join(ROOT, 'data', 'content', 'products')
export const PILOT_SKUS = ['JV-D1000', 'JV-D4000', 'JV-TURMERIC500']
export function readJson(path: string, fallback: any = null) {
if (!existsSync(path)) return fallback
return JSON.parse(readFileSync(path, 'utf8').replace(/^\uFEFF/, ''))
}
export function writeJson(path: string, data: any) {
mkdirSync(join(path, '..'), { recursive: true })
writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf8')
}
export function intelPath(sku: string, file: string) { return join(INTEL_ROOT, sku, file) }
export function sourcePath(...parts: string[]) { return join(SOURCES_ROOT, ...parts) }
export function now() { return new Date().toISOString() }
export function slug(value: string) { return String(value || '').toLowerCase().replace(/&/g, 'and').replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') }
export function stripHtml(html: string) {
return String(html || '')
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
.replace(/<[^>]+>/g, ' ')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/\s+/g, ' ')
.trim()
}
export function listJson(dir: string) {
if (!existsSync(dir)) return []
return readdirSync(dir).filter(f => f.endsWith('.json')).map(f => join(dir, f))
}
export function sourceStatus(kind: string) {
const dir = sourcePath(kind)
const files = listJson(dir)
return files.length ? `available:${files.length}` : 'missing'
}
export function loadCompetitorConfig() {
return readJson(sourcePath('competitor-sources.json'), { competitors: [] }).competitors || []
}
export function listProducts() {
if (!existsSync(CONTENT_PRODUCTS_ROOT)) return []
return readdirSync(CONTENT_PRODUCTS_ROOT)
.filter(file => file.endsWith('.json'))
.sort()
.map(file => readJson(join(CONTENT_PRODUCTS_ROOT, file), {}))
}
export function readPilotDoc(sku: string) {
return {
aspects: readJson(intelPath(sku, 'review-aspects.json'), {}),
sw: readJson(intelPath(sku, 'strengths-weaknesses.json'), {}),
blockers: readJson(intelPath(sku, 'conversion-blockers.json'), {}),
improvements: readJson(intelPath(sku, 'improvements.json'), {})
}
}