Bera
Deploy Genesis AI to Hugging Face Spaces
1b2f7eb
/**
* 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(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Genesis AI β€” Building Dashboard…</title>
<style>
body { font-family: 'Segoe UI', sans-serif; background: #f5f0e8;
display: flex; align-items: center; justify-content: center;
height: 100vh; margin: 0; }
.box { background: #fff; border: 1px solid rgba(26,107,47,.2);
border-radius: 12px; padding: 40px 48px; text-align: center;
box-shadow: 0 4px 20px rgba(0,0,0,.08); max-width: 480px; }
h1 { font-size: 20px; color: #1a6b2f; margin-bottom: 8px; }
p { color: #6b6b6b; font-size: 14px; line-height: 1.6; }
pre { background: #f0ead8; border-radius: 7px; padding: 14px 18px;
font-size: 12px; text-align: left; color: #2c2c2c; margin-top: 20px;
overflow-x: auto; }
.badge { display:inline-block; background:rgba(26,107,47,.1);
color:#1a6b2f; padding:3px 10px; border-radius:12px;
font-size:11px; font-weight:600; margin-bottom:16px; }
</style>
</head>
<body>
<div class="box">
<div class="badge">Genesis AI</div>
<h1>Dashboard not yet built</h1>
<p>The Python pipeline has not run yet, or the JSON artefacts are missing
from <code>/output/</code>. Run the pipeline first, then rebuild:</p>
<pre>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</pre>
<p>Or POST to <code>/api/build</code> if artefacts already exist in <code>/output/</code>.</p>
</div>
</body>
</html>`);
}
});
// ── 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}`);
});