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