/** * Genesis AI — Price Elasticity Intelligence Platform * Node.js / Express server for Hugging Face Spaces deployment * * Routes: * GET / → serves the built dashboard HTML * GET /health → health check (HF uses this) * POST /api/build → triggers a fresh dashboard build from /output JSON files * GET /api/data/:key → serves individual JSON artefact files */ const express = require('express'); const path = require('path'); const fs = require('fs'); const { execSync } = require('child_process'); const app = express(); const PORT = process.env.PORT || 7860; // Hugging Face requires 7860 // ── Paths ──────────────────────────────────────────────────────────────────── const ROOT = __dirname; const PUBLIC_DIR = path.join(ROOT, 'public'); const OUTPUT_DIR = path.join(ROOT, 'output'); // JSON artefacts from Python pipeline const DASH_HTML = path.join(PUBLIC_DIR, 'dashboard.html'); // ── Middleware ─────────────────────────────────────────────────────────────── app.use(express.json()); app.use(express.static(PUBLIC_DIR)); // ── Health check (Hugging Face pings this) ─────────────────────────────────── app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // ── Serve dashboard ────────────────────────────────────────────────────────── app.get('/', (req, res) => { if (fs.existsSync(DASH_HTML)) { res.sendFile(DASH_HTML); } else { // Dashboard not built yet — show a friendly waiting page res.send(` Genesis AI — Building Dashboard…
Genesis AI

Dashboard not yet built

The Python pipeline has not run yet, or the JSON artefacts are missing from /output/. Run the pipeline first, then rebuild:

python elasticity_model.py \\
  --input  Brand_Onboarding_Template.xlsx \\
  --focal  "DABUR SARSON AMLA" \\
  --output ./output/

python dashboard_builder.py \\
  --data-dir ./output/ \\
  --brand    "DABUR SARSON AMLA" \\
  --css      public/dashboard.css \\
  --js       public/dashboard.js \\
  --out      public/dashboard.html

Or POST to /api/build if artefacts already exist in /output/.

`); } }); // ── API: serve individual JSON artefacts ────────────────────────────────────── // GET /api/data/models → output/models.json // GET /api/data/trend → output/trend.json … etc. const JSON_MAP = { models: 'models.json', recs: 'recs_full.json', trend: 'trend.json', ms: 'ms.json', vol_salience: 'vol_salience.json', val_share: 'val_share.json', freq_anchors: 'freq_anchors.json', ppa_mt: 'ppa_mt.json', ppa_tt: 'ppa_tt.json', comp_ms: 'comp_ms.json', vtm: 'vtm.json', interaction: 'interaction.json', growth_decomp: 'growth_decomp.json', pgi: 'pgi.json', stats: 'stats.json', }; app.get('/api/data/:key', (req, res) => { const fname = JSON_MAP[req.params.key]; if (!fname) return res.status(404).json({ error: 'Unknown data key' }); const fpath = path.join(OUTPUT_DIR, fname); if (!fs.existsSync(fpath)) { return res.status(404).json({ error: `File not found: ${fname}` }); } res.setHeader('Content-Type', 'application/json'); res.sendFile(fpath); }); // ── API: trigger a dashboard rebuild ────────────────────────────────────────── // POST /api/build body: { brand: "DABUR SARSON AMLA" } app.post('/api/build', (req, res) => { const brand = req.body?.brand || 'DABUR SARSON AMLA'; // Check JSON artefacts exist const needed = ['models.json', 'recs_full.json', 'trend.json']; const missing = needed.filter(f => !fs.existsSync(path.join(OUTPUT_DIR, f))); if (missing.length) { return res.status(400).json({ error: 'Missing JSON artefacts — run elasticity_model.py first', missing, }); } try { const cmd = [ 'python3 dashboard_builder.py', `--data-dir "${OUTPUT_DIR}"`, `--brand "${brand}"`, `--css "${path.join(PUBLIC_DIR, 'dashboard.css')}"`, `--js "${path.join(PUBLIC_DIR, 'dashboard.js')}"`, `--out "${DASH_HTML}"`, ].join(' '); execSync(cmd, { cwd: ROOT, stdio: 'pipe' }); const size = fs.statSync(DASH_HTML).size; res.json({ ok: true, message: 'Dashboard rebuilt successfully', file: DASH_HTML, size_kb: Math.round(size / 1024), }); } catch (err) { res.status(500).json({ error: 'Build failed', detail: err.stderr?.toString() || err.message, }); } }); // ── API: list available JSON artefacts ──────────────────────────────────────── app.get('/api/status', (req, res) => { const files = Object.entries(JSON_MAP).map(([key, fname]) => ({ key, file: fname, exists: fs.existsSync(path.join(OUTPUT_DIR, fname)), })); res.json({ dashboard_built: fs.existsSync(DASH_HTML), artefacts: files, }); }); // ── Start ───────────────────────────────────────────────────────────────────── app.listen(PORT, '0.0.0.0', () => { console.log(` ╔════════════════════════════════════════════════════╗ ║ Genesis AI — Price Elasticity Intelligence ║ ║ Server running on http://0.0.0.0:${PORT} ║ ╚════════════════════════════════════════════════════╝ `); console.log(`Dashboard built: ${fs.existsSync(DASH_HTML) ? '✓ YES' : '✗ NO (run pipeline first)'}`); console.log(`Artefacts dir : ${OUTPUT_DIR}`); });