← back to Travis
server.js
97 lines
// ============================================================================
// Travis — Stereo Report Viewer
// Port: 7810 area (will pick 7350) | Auth: admin / DWSecure2024!
// One-page stereo report with pie chart, PDF export, logo upload
// ============================================================================
const express = require('express');
const helmet = require('helmet');
const fs = require('fs');
const path = require('path');
const app = express();
// Security headers via helmet (added 2026-05-04 overnight YOLO loop)
app.use(helmet({ contentSecurityPolicy: false }));
const PORT = 7350;
app.use(express.json({ limit: '10mb' }));
app.use(express.static(path.join(__dirname, 'public')));
// ── Parse CSV data ──
function loadData() {
const csv = fs.readFileSync(path.join(__dirname, 'stereo-data.csv'), 'utf8');
const lines = csv.split('\n').filter(l => l.trim());
const headers = lines[0].split(',');
const rows = lines.slice(1).map(line => {
const vals = [];
let current = '';
let inQuote = false;
for (const ch of line) {
if (ch === '"') { inQuote = !inQuote; continue; }
if (ch === ',' && !inQuote) { vals.push(current.trim()); current = ''; continue; }
current += ch;
}
vals.push(current.trim());
const obj = {};
headers.forEach((h, i) => obj[h.trim()] = vals[i] || '');
return obj;
});
return rows;
}
// ── API: Report data ──
app.get('/api/report', (req, res) => {
const rows = loadData();
const total = rows.length;
const active = rows.filter(r => r.Status_InCut === 'Active');
const inactive = rows.filter(r => r.Status_InCut === 'Inactive');
const turnedOver = active.filter(r => r.Status3D === 'Turned Over');
const nto = active.filter(r => r.Status3D === 'NTO');
const hold = active.filter(r => r.Status3D === 'HOLD');
const other = active.filter(r => !['Turned Over', 'NTO', 'HOLD'].includes(r.Status3D));
const vfx = active.filter(r => r.Shot_Type === 'VFX');
const nvx = active.filter(r => r.Shot_Type === 'NVX');
const totalFrames = active.reduce((s, r) => s + (parseInt(r.Frames_TotalInCut) || 0), 0);
const vfxFrames = vfx.reduce((s, r) => s + (parseInt(r.Frames_TotalInCut) || 0), 0);
const nvxFrames = nvx.reduce((s, r) => s + (parseInt(r.Frames_TotalInCut) || 0), 0);
res.json({
date: new Date().toISOString(),
totalRows: total,
excludedInactive: inactive.length,
activeShots: active.length,
turnover: {
turnedOver: turnedOver.length,
nto: nto.length,
hold: hold.length,
other: other.length,
},
shotTypes: {
vfx: vfx.length,
nvx: nvx.length,
vfxFrames,
nvxFrames,
},
totalFrames,
totalMinutes: +(totalFrames / 24 / 60).toFixed(1),
percentTurnedOver: active.length ? +((turnedOver.length / active.length) * 100).toFixed(1) : 0,
percentNTO: active.length ? +((nto.length / active.length) * 100).toFixed(1) : 0,
percentHold: active.length ? +((hold.length / active.length) * 100).toFixed(1) : 0,
});
});
app.get('/health', (req, res) => {
res.json({ status: 'ok', agent: 'Travis', role: 'Stereo Report' });
});
// ── 404 fallback (after static + routes) ──
app.use((req, res) => {
res.status(404).json({ error: 'not_found', path: req.path });
});
app.listen(PORT, '0.0.0.0', () => {
console.log(`Travis — Stereo Report live on port ${PORT}`);
});