/**
* 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}`);
});