← back to Designer Wallcoverings

DW-Agents/dw-agents/agent-executive-cfo/cfo-dashboard-agent.ts

740 lines

import cookieParser from 'cookie-parser';
/**
 * DW-Agents: CFO Executive Dashboard
 *
 * Financial oversight and analysis for Designer Wallcoverings:
 * - Live revenue tracking (today, week, month) from Shopify
 * - Order analytics with financial/fulfillment status
 * - Vendor catalog investment breakdown from PostgreSQL
 * - Revenue trend comparison (today vs yesterday)
 * - Action items auto-detected from financial data
 * - AI-powered financial advisor chat
 * - Inter-agent messaging system
 *
 * Port: 7121
 * PM2: dw-executive-cfo
 */

import Anthropic from '@anthropic-ai/sdk';
import express, { Request, Response } from 'express';
import { requireGlobalAuth } from '../shared-global-auth';
import session from 'express-session';
import dotenv from 'dotenv';
import path from 'path';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import fetch from 'node-fetch';
import {
  pool, queryDB, shopifyGraphQL, getShopifyOrders, getShopifyProductCount,
  getPM2Status, getVendorStats, EXECUTIVE_AGENTS, SERVER_IP,
  sendMessage, createMessageStore, createActionItemStore,
  execDashboardHead, execNavBar, execChatPanel,
  formatUptime, formatCurrency, formatNumber, timeAgo,
  autoRefreshScript, notifySlack, getShopifyCustomerCount
} from '../shared-executive-core';
import { chatMiddleware } from '../shared-chat-integration';

// Fix __dirname for ES modules
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Load environment variables
dotenv.config({ path: path.join(__dirname, '..', '.env') });

const app = express();
const PORT = 7121;

// Stores
const messageStore = createMessageStore();
const actionStore = createActionItemStore();

// Anthropic AI
const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY || '',
});

// Middleware
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Chat widget injection
app.use(chatMiddleware({
  agentId: 'agent-executive-cfo',
  agentName: 'CFO Dashboard',
  port: PORT,
  category: 'executive'
}));

declare module 'express-session' {
  interface SessionData {
    authenticated: boolean;
    chatHistory: Array<{ role: 'user' | 'assistant'; content: string }>;
  }
}

app.use(session({
  secret: 'dw-cfo-dashboard-2025-finance',
  resave: false,
  saveUninitialized: true,
  rolling: true,
  cookie: { secure: false, httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000 }
}));

// ─── Sub-agents the CFO has direct access to ────────────────────────────
const CFO_SUB_AGENTS = [
  { name: 'Accounting', emoji: '📒', port: 9882, desc: 'Revenue tracking & reconciliation' },
  { name: 'Purchasing', emoji: '🛒', port: 9880, desc: 'Vendor orders & cost management' },
  { name: 'Shopify Store', emoji: '🛍️', port: 7238, desc: 'E-commerce operations & sales' },
  { name: 'Digital Samples', emoji: '🎨', port: 9879, desc: 'Sample fulfillment & tracking' }
];

// ─── Login / Logout ─────────────────────────────────────────────────────
app.get('/login', requireGlobalAuth, (req, res) => { res.redirect('/'); });
app.post('/login', requireGlobalAuth, (req, res) => { res.redirect('/'); });
app.get('/logout', (req, res) => {
  req.session.destroy(() => res.redirect('/login'));
});

// Helper: wrap async call with timeout and fallback
function withTimeout<T>(promise: Promise<T>, fallback: T, ms = 15000): Promise<T> {
  return Promise.race([
    promise,
    new Promise<T>((resolve) => setTimeout(() => resolve(fallback), ms))
  ]);
}

// ─── Main Dashboard ─────────────────────────────────────────────────────
app.get('/', requireGlobalAuth, async (req: Request, res: Response) => {
  try {
    // Fetch all financial data in parallel with 15s timeout per call
    const defaultOrders = { orders: 0, revenue: 0, avgValue: 0 };
    const [todayData, weekData, monthData, yesterdayData, vendorStats, customerCount, recentOrdersData] = await Promise.all([
      withTimeout(getShopifyOrders('today'), defaultOrders),
      withTimeout(getShopifyOrders('week'), defaultOrders),
      withTimeout(getShopifyOrders('month'), defaultOrders),
      withTimeout(getYesterdayOrders(), defaultOrders),
      withTimeout(getVendorStats(), []),
      withTimeout(getShopifyCustomerCount(), 0),
      withTimeout(getRecentOrders(), [])
    ]);

    // Revenue trend calculation
    const todayRev = todayData.revenue;
    const yesterdayRev = yesterdayData.revenue;
    const weekAvg = weekData.orders > 0 ? weekData.revenue / 7 : 0;
    const revChange = yesterdayRev > 0 ? ((todayRev - yesterdayRev) / yesterdayRev * 100) : 0;
    const revTrendDir = revChange >= 0 ? 'up' : 'down';
    const revTrendClass = revChange >= 0 ? 'green' : 'red';
    const revTrendArrow = revChange >= 0 ? '&#9650;' : '&#9660;';

    // Auto-detect action items
    actionStore.getAll().length === 0 && detectActionItems(todayRev, weekAvg, vendorStats);

    // Build order rows HTML
    const orderRows = (recentOrdersData || []).map((o: any) => {
      const fin = o.financialStatus || 'UNKNOWN';
      const ful = o.fulfillmentStatus || 'UNFULFILLED';
      const finClass = fin === 'PAID' ? 'green' : fin === 'PENDING' ? 'amber' : 'red';
      const fulClass = ful === 'FULFILLED' ? 'green' : ful === 'UNFULFILLED' ? 'amber' : 'blue';
      const created = new Date(o.createdAt);
      const dateStr = created.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
      const timeStr = created.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
      return `<tr>
        <td style="font-weight:600;color:#3b82f6">${o.name}</td>
        <td>${dateStr} ${timeStr}</td>
        <td style="font-weight:600">${formatCurrency(parseFloat(o.amount || '0'))}</td>
        <td><span class="badge ${finClass}">${fin}</span></td>
        <td><span class="badge ${fulClass}">${ful}</span></td>
      </tr>`;
    }).join('');

    // Vendor investment rows
    const vendorRows = vendorStats.map(v => {
      const pct = v.total > 0 ? Math.round(v.imported / v.total * 100) : 0;
      const pctClass = pct >= 50 ? 'green' : pct >= 10 ? 'amber' : 'red';
      return `<tr>
        <td style="font-weight:600">${v.vendor}</td>
        <td>${formatNumber(v.total)}</td>
        <td>${formatNumber(v.imported)}</td>
        <td><span class="badge ${pctClass}">${pct}%</span></td>
      </tr>`;
    }).join('');

    // Sub-agent cards
    const subAgentCards = CFO_SUB_AGENTS.map(a =>
      `<a href="http://${SERVER_IP}:${a.port}" target="_blank" class="exec-card">
        <div class="emoji">${a.emoji}</div>
        <div class="name">${a.name}</div>
        <div class="role">${a.desc}</div>
      </a>`
    ).join('');

    // Messages HTML
    const msgs = messageStore.getAll();
    const msgsHtml = msgs.length > 0
      ? `<ul class="msg-list">${msgs.map(m => `<li class="msg-item ${m.read ? '' : 'unread'}">
          <span class="from">${m.from}</span>
          <div>
            <div class="subject">${m.subject} <span class="priority-badge ${m.priority}">${m.priority}</span></div>
            <div class="body">${m.body.substring(0, 120)}${m.body.length > 120 ? '...' : ''}</div>
          </div>
          <span class="time">${timeAgo(m.timestamp)}</span>
        </li>`).join('')}</ul>`
      : '<p style="color:#64748b;font-size:13px;padding:16px;">No messages yet.</p>';

    // Action items HTML
    const actions = actionStore.getUnresolved();
    const actionsHtml = actions.length > 0
      ? actions.map(a => `<div class="action-item ${a.severity}">
          <div class="title">${a.severity === 'critical' ? '!!' : a.severity === 'warning' ? '!' : 'i'} ${a.title}</div>
          <div class="desc">${a.description}</div>
          <div class="meta">${a.source} - ${timeAgo(a.timestamp)}</div>
        </div>`).join('')
      : '<p style="color:#64748b;font-size:13px;">No action items. All financials within expected ranges.</p>';

    // Assemble full HTML
    const html = `${execDashboardHead('CFO Dashboard', '@cfo')}
${execNavBar('CFO Dashboard', '💰', '@cfo', messageStore.unreadCount())}
<div class="main">
  <div class="content">

    <!-- KPI Cards -->
    <div class="kpi-grid">
      <div class="kpi-card green">
        <div class="label">Revenue Today</div>
        <div class="value">${formatCurrency(todayData.revenue)}</div>
        <div class="sub">${todayData.orders} order${todayData.orders !== 1 ? 's' : ''} today</div>
      </div>
      <div class="kpi-card blue">
        <div class="label">Revenue This Week</div>
        <div class="value">${formatCurrency(weekData.revenue)}</div>
        <div class="sub">${weekData.orders} orders / 7 days</div>
      </div>
      <div class="kpi-card">
        <div class="label">Revenue This Month</div>
        <div class="value">${formatCurrency(monthData.revenue)}</div>
        <div class="sub">${monthData.orders} orders MTD</div>
      </div>
      <div class="kpi-card ${todayData.orders > 0 ? 'green' : 'amber'}">
        <div class="label">Orders Today</div>
        <div class="value">${formatNumber(todayData.orders)}</div>
        <div class="sub">${todayData.orders > 0 ? 'Active selling day' : 'No orders yet'}</div>
      </div>
      <div class="kpi-card blue">
        <div class="label">Avg Order Value</div>
        <div class="value">${formatCurrency(monthData.avgValue)}</div>
        <div class="sub">Month average</div>
      </div>
      <div class="kpi-card">
        <div class="label">Total Customers</div>
        <div class="value">${formatNumber(customerCount)}</div>
        <div class="sub">Shopify customer base</div>
      </div>
    </div>

    <!-- Revenue Trend -->
    <div class="section">
      <h2>📈 Revenue Trend</h2>
      <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px;">
        <div style="background:#0f172a;border:1px solid #334155;border-radius:10px;padding:20px;">
          <div style="color:#94a3b8;font-size:12px;text-transform:uppercase;margin-bottom:8px;">Yesterday</div>
          <div style="font-size:24px;font-weight:700;color:#f8fafc;">${formatCurrency(yesterdayRev)}</div>
          <div style="color:#64748b;font-size:12px;">${yesterdayData.orders} orders</div>
        </div>
        <div style="background:#0f172a;border:1px solid #334155;border-radius:10px;padding:20px;">
          <div style="color:#94a3b8;font-size:12px;text-transform:uppercase;margin-bottom:8px;">Today</div>
          <div style="font-size:24px;font-weight:700;color:#f8fafc;">${formatCurrency(todayRev)}</div>
          <div style="color:#64748b;font-size:12px;">${todayData.orders} orders</div>
        </div>
        <div style="background:#0f172a;border:1px solid ${revTrendClass === 'green' ? '#166534' : '#7f1d1d'};border-radius:10px;padding:20px;">
          <div style="color:#94a3b8;font-size:12px;text-transform:uppercase;margin-bottom:8px;">Day-over-Day</div>
          <div style="font-size:24px;font-weight:700;color:${revTrendClass === 'green' ? '#22c55e' : '#ef4444'};">${revTrendArrow} ${Math.abs(revChange).toFixed(1)}%</div>
          <div style="color:#64748b;font-size:12px;">vs yesterday</div>
        </div>
      </div>
    </div>

    <!-- Order Analytics -->
    <div class="section">
      <h2>📋 Order Analytics (Recent 20)</h2>
      <div style="overflow-x:auto;">
        <table class="agent-table">
          <thead>
            <tr>
              <th>Order #</th>
              <th>Date</th>
              <th>Amount</th>
              <th>Financial</th>
              <th>Fulfillment</th>
            </tr>
          </thead>
          <tbody>
            ${orderRows || '<tr><td colspan="5" style="text-align:center;color:#64748b;">No recent orders found</td></tr>'}
          </tbody>
        </table>
      </div>
    </div>

    <!-- Vendor Catalog Investment -->
    <div class="section">
      <h2>🏭 Vendor Catalog Investment</h2>
      <div style="overflow-x:auto;">
        <table class="agent-table">
          <thead>
            <tr>
              <th>Vendor</th>
              <th>Total Products</th>
              <th>Imported to Shopify</th>
              <th>% Imported</th>
            </tr>
          </thead>
          <tbody>
            ${vendorRows || '<tr><td colspan="4" style="text-align:center;color:#64748b;">No vendor data available</td></tr>'}
          </tbody>
        </table>
      </div>
    </div>

    <!-- Sub-Agents -->
    <div class="section">
      <h2>💼 Financial Sub-Agents</h2>
      <div class="exec-grid">
        ${subAgentCards}
      </div>
    </div>

    <!-- Skills & Capabilities -->
    <div class="section">
      <details style="cursor:pointer;">
        <summary style="font-size:16px;font-weight:700;color:#f8fafc;padding:8px 0;list-style:none;display:flex;align-items:center;gap:8px;">
          <span style="transition:transform 0.2s;display:inline-block;">&#9654;</span> Skills &amp; Capabilities
          <span style="font-size:12px;color:#94a3b8;margin-left:auto;">(10 skills)</span>
        </summary>
        <table style="width:100%;border-collapse:collapse;margin-top:12px;">
          <thead>
            <tr style="border-bottom:1px solid #334155;">
              <th style="text-align:left;padding:8px 12px;font-size:12px;color:#94a3b8;font-weight:600;">Skill</th>
              <th style="text-align:left;padding:8px 12px;font-size:12px;color:#94a3b8;font-weight:600;">Description</th>
              <th style="text-align:center;padding:8px 12px;font-size:12px;color:#94a3b8;font-weight:600;">Status</th>
            </tr>
          </thead>
          <tbody>
            <tr style="border-bottom:1px solid #1e293b;">
              <td style="padding:8px 12px;font-size:13px;font-weight:600;color:#e2e8f0;">Revenue Dashboard</td>
              <td style="padding:8px 12px;font-size:12px;color:#94a3b8;">Daily/weekly/monthly Shopify revenue tracking</td>
              <td style="padding:8px 12px;text-align:center;"><span style="font-size:11px;padding:2px 8px;border-radius:4px;background:#065f46;color:#34d399;">Active</span></td>
            </tr>
            <tr style="border-bottom:1px solid #1e293b;">
              <td style="padding:8px 12px;font-size:13px;font-weight:600;color:#e2e8f0;">Order Analytics</td>
              <td style="padding:8px 12px;font-size:12px;color:#94a3b8;">Order count, avg value, fulfillment rates</td>
              <td style="padding:8px 12px;text-align:center;"><span style="font-size:11px;padding:2px 8px;border-radius:4px;background:#065f46;color:#34d399;">Active</span></td>
            </tr>
            <tr style="border-bottom:1px solid #1e293b;">
              <td style="padding:8px 12px;font-size:13px;font-weight:600;color:#e2e8f0;">Vendor Cost Analysis</td>
              <td style="padding:8px 12px;font-size:12px;color:#94a3b8;">Products per vendor, import cost tracking</td>
              <td style="padding:8px 12px;text-align:center;"><span style="font-size:11px;padding:2px 8px;border-radius:4px;background:#065f46;color:#34d399;">Active</span></td>
            </tr>
            <tr style="border-bottom:1px solid #1e293b;">
              <td style="padding:8px 12px;font-size:13px;font-weight:600;color:#e2e8f0;">Sample Order Revenue</td>
              <td style="padding:8px 12px;font-size:12px;color:#94a3b8;">DIG-series sample order financial tracking</td>
              <td style="padding:8px 12px;text-align:center;"><span style="font-size:11px;padding:2px 8px;border-radius:4px;background:#065f46;color:#34d399;">Active</span></td>
            </tr>
            <tr style="border-bottom:1px solid #1e293b;">
              <td style="padding:8px 12px;font-size:13px;font-weight:600;color:#e2e8f0;">Financial Anomaly Detection</td>
              <td style="padding:8px 12px;font-size:12px;color:#94a3b8;">Revenue drop/spike auto-detection (&gt;10%)</td>
              <td style="padding:8px 12px;text-align:center;"><span style="font-size:11px;padding:2px 8px;border-radius:4px;background:#065f46;color:#34d399;">Active</span></td>
            </tr>
            <tr style="border-bottom:1px solid #1e293b;">
              <td style="padding:8px 12px;font-size:13px;font-weight:600;color:#e2e8f0;">Accounting Integration</td>
              <td style="padding:8px 12px;font-size:12px;color:#94a3b8;">Subordinate accounting agent data aggregation</td>
              <td style="padding:8px 12px;text-align:center;"><span style="font-size:11px;padding:2px 8px;border-radius:4px;background:#065f46;color:#34d399;">Active</span></td>
            </tr>
            <tr style="border-bottom:1px solid #1e293b;">
              <td style="padding:8px 12px;font-size:13px;font-weight:600;color:#e2e8f0;">Purchasing Oversight</td>
              <td style="padding:8px 12px;font-size:12px;color:#94a3b8;">Vendor spend and purchase order review</td>
              <td style="padding:8px 12px;text-align:center;"><span style="font-size:11px;padding:2px 8px;border-radius:4px;background:#065f46;color:#34d399;">Active</span></td>
            </tr>
            <tr style="border-bottom:1px solid #1e293b;">
              <td style="padding:8px 12px;font-size:13px;font-weight:600;color:#e2e8f0;">AI Financial Analyst</td>
              <td style="padding:8px 12px;font-size:12px;color:#94a3b8;">Claude-powered financial insights and forecasting</td>
              <td style="padding:8px 12px;text-align:center;"><span style="font-size:11px;padding:2px 8px;border-radius:4px;background:#065f46;color:#34d399;">Active</span></td>
            </tr>
            <tr style="border-bottom:1px solid #1e293b;">
              <td style="padding:8px 12px;font-size:13px;font-weight:600;color:#e2e8f0;">Inter-Agent Financial Reports</td>
              <td style="padding:8px 12px;font-size:12px;color:#94a3b8;">@mention revenue alerts to CEO</td>
              <td style="padding:8px 12px;text-align:center;"><span style="font-size:11px;padding:2px 8px;border-radius:4px;background:#065f46;color:#34d399;">Active</span></td>
            </tr>
            <tr style="border-bottom:1px solid #1e293b;">
              <td style="padding:8px 12px;font-size:13px;font-weight:600;color:#e2e8f0;">Profit Margin Analysis</td>
              <td style="padding:8px 12px;font-size:12px;color:#94a3b8;">Cost vs revenue per vendor tracking</td>
              <td style="padding:8px 12px;text-align:center;"><span style="font-size:11px;padding:2px 8px;border-radius:4px;background:#065f46;color:#34d399;">Active</span></td>
            </tr>
          </tbody>
        </table>
      </details>
    </div>
    <style>details[open] > summary span:first-child { transform: rotate(90deg); } details summary::-webkit-details-marker { display: none; }</style>

    <!-- Messages -->
    <div class="section" id="messages-section">
      <h2>📨 Messages ${messageStore.unreadCount() > 0 ? `<span class="msg-badge">${messageStore.unreadCount()}</span>` : ''}</h2>
      <div style="margin-bottom:12px;">
        <button onclick="document.getElementById('sendModal').style.display='block'" style="background:#3b82f6;color:#fff;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;font-size:13px;font-weight:600;">Send Message</button>
      </div>
      ${msgsHtml}
    </div>

    <!-- Action Items -->
    <div class="section">
      <h2>⚡ Action Items</h2>
      ${actionsHtml}
    </div>

  </div>

  <!-- Chat Panel -->
  ${execChatPanel('CFO', 'Financial Advisor')}
</div>

<!-- Send Message Modal -->
<div id="sendModal" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.6);z-index:9999;display:none;">
  <div style="background:#1e293b;border:1px solid #334155;border-radius:12px;max-width:500px;margin:10% auto;padding:24px;">
    <h3 style="color:#f8fafc;margin-bottom:16px;">Send Message</h3>
    <div style="margin-bottom:12px;">
      <label style="color:#94a3b8;font-size:12px;display:block;margin-bottom:4px;">To</label>
      <select id="msgTo" style="width:100%;padding:8px;background:#0f172a;border:1px solid #475569;border-radius:6px;color:#f8fafc;font-size:13px;">
        ${Object.entries(EXECUTIVE_AGENTS).filter(([h]) => h !== '@cfo').map(([h, a]) => `<option value="${h}">${a.emoji} ${a.name}</option>`).join('')}
        <option value="@all">All Executives</option>
      </select>
    </div>
    <div style="margin-bottom:12px;">
      <label style="color:#94a3b8;font-size:12px;display:block;margin-bottom:4px;">Subject</label>
      <input id="msgSubject" style="width:100%;padding:8px;background:#0f172a;border:1px solid #475569;border-radius:6px;color:#f8fafc;font-size:13px;" placeholder="Message subject">
    </div>
    <div style="margin-bottom:12px;">
      <label style="color:#94a3b8;font-size:12px;display:block;margin-bottom:4px;">Body</label>
      <textarea id="msgBody" rows="4" style="width:100%;padding:8px;background:#0f172a;border:1px solid #475569;border-radius:6px;color:#f8fafc;font-size:13px;resize:vertical;" placeholder="Message body"></textarea>
    </div>
    <div style="margin-bottom:12px;">
      <label style="color:#94a3b8;font-size:12px;display:block;margin-bottom:4px;">Priority</label>
      <select id="msgPriority" style="width:100%;padding:8px;background:#0f172a;border:1px solid #475569;border-radius:6px;color:#f8fafc;font-size:13px;">
        <option value="normal">Normal</option>
        <option value="high">High</option>
        <option value="critical">Critical</option>
        <option value="low">Low</option>
      </select>
    </div>
    <div style="display:flex;gap:8px;justify-content:flex-end;">
      <button onclick="document.getElementById('sendModal').style.display='none'" style="background:#334155;color:#94a3b8;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;font-size:13px;">Cancel</button>
      <button onclick="sendMsg()" style="background:#3b82f6;color:#fff;border:none;padding:8px 16px;border-radius:6px;cursor:pointer;font-size:13px;font-weight:600;">Send</button>
    </div>
  </div>
</div>

<style>
  .badge { font-size:11px; padding:2px 8px; border-radius:4px; font-weight:600; text-transform:uppercase; }
  .badge.green { background:#166534; color:#4ade80; }
  .badge.amber { background:#78350f; color:#fcd34d; }
  .badge.red { background:#7f1d1d; color:#fca5a5; }
  .badge.blue { background:#1e3a5f; color:#93c5fd; }
</style>

<script>
async function sendMsg() {
  const to = document.getElementById('msgTo').value;
  const subject = document.getElementById('msgSubject').value;
  const body = document.getElementById('msgBody').value;
  const priority = document.getElementById('msgPriority').value;
  if (!subject || !body) { alert('Subject and body required'); return; }
  try {
    const resp = await fetch('/api/send-message', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ to, subject, body, priority })
    });
    const data = await resp.json();
    if (data.success) {
      alert('Message sent');
      document.getElementById('sendModal').style.display = 'none';
      document.getElementById('msgSubject').value = '';
      document.getElementById('msgBody').value = '';
    } else {
      alert('Failed: ' + (data.error || 'Unknown error'));
    }
  } catch(e) { alert('Error: ' + e.message); }
}
</script>

${autoRefreshScript(30000)}
</body>
</html>`;

    res.send(html);
  } catch (err: any) {
    console.error('[CFO Dashboard] Render error:', err.message);
    res.status(500).send(`<h1>CFO Dashboard Error</h1><pre>${err.message}</pre>`);
  }
});

// ─── API: Financial Metrics ─────────────────────────────────────────────
app.get('/api/metrics', requireGlobalAuth, async (req: Request, res: Response) => {
  try {
    const defaultOrders = { orders: 0, revenue: 0, avgValue: 0 };
    const [todayData, weekData, monthData, vendorStats, customerCount] = await Promise.all([
      withTimeout(getShopifyOrders('today'), defaultOrders),
      withTimeout(getShopifyOrders('week'), defaultOrders),
      withTimeout(getShopifyOrders('month'), defaultOrders),
      withTimeout(getVendorStats(), []),
      withTimeout(getShopifyCustomerCount(), 0)
    ]);

    res.json({
      status: 'online',
      agent: 'CFO Dashboard',
      port: PORT,
      uptime: process.uptime(),
      lastActivity: new Date().toISOString(),
      financial: {
        revenueToday: todayData.revenue,
        revenueWeek: weekData.revenue,
        revenueMonth: monthData.revenue,
        ordersToday: todayData.orders,
        ordersWeek: weekData.orders,
        ordersMonth: monthData.orders,
        avgOrderValueMonth: monthData.avgValue,
        totalCustomers: customerCount
      },
      vendors: vendorStats.map(v => ({
        name: v.vendor,
        totalProducts: v.total,
        importedToShopify: v.imported,
        percentImported: v.total > 0 ? Math.round(v.imported / v.total * 100) : 0
      })),
      messages: {
        total: messageStore.count(),
        unread: messageStore.unreadCount()
      },
      actionItems: actionStore.count()
    });
  } catch (err: any) {
    console.error('[CFO Metrics Error]', err.message);
    res.json({
      status: 'online',
      agent: 'CFO Dashboard',
      port: PORT,
      uptime: process.uptime(),
      error: err.message
    });
  }
});

// ─── API: Messages ──────────────────────────────────────────────────────
app.get('/api/messages', requireGlobalAuth, (req: Request, res: Response) => {
  res.json({
    messages: messageStore.getAll(),
    unread: messageStore.unreadCount(),
    total: messageStore.count()
  });
});

// ─── API: Receive Message ───────────────────────────────────────────────
app.post('/api/message', (req: Request, res: Response) => {
  try {
    const msg = req.body;
    if (msg && msg.id && msg.from && msg.subject) {
      messageStore.receive(msg);
      console.log(`[CFO] Message received from ${msg.from}: ${msg.subject}`);
      res.json({ success: true });
    } else {
      res.status(400).json({ success: false, error: 'Invalid message format' });
    }
  } catch (err: any) {
    res.status(500).json({ success: false, error: err.message });
  }
});

// ─── API: Send Message ──────────────────────────────────────────────────
app.post('/api/send-message', requireGlobalAuth, async (req: Request, res: Response) => {
  try {
    const { to, subject, body, priority } = req.body;
    if (!to || !subject || !body) {
      return res.status(400).json({ success: false, error: 'Missing required fields: to, subject, body' });
    }
    const sent = await sendMessage('@cfo', to, subject, body, priority || 'normal');
    res.json({ success: sent, message: sent ? 'Message sent' : 'Failed to deliver' });
  } catch (err: any) {
    res.status(500).json({ success: false, error: err.message });
  }
});

// ─── API: Chat (Claude Financial Advisor) ───────────────────────────────
app.post('/api/chat', requireGlobalAuth, async (req: Request, res: Response) => {
  try {
    const { message, history } = req.body;
    if (!message) {
      return res.status(400).json({ error: 'Message required' });
    }

    // Initialize session chat history
    if (!req.session.chatHistory) {
      req.session.chatHistory = [];
    }
    req.session.chatHistory.push({ role: 'user', content: message });

    // Gather live financial context if query relates to finances
    let financialContext = '';
    const lowerMsg = message.toLowerCase();
    if (lowerMsg.match(/revenue|sales|order|finance|money|cost|margin|profit|budget|expense|vendor|product|customer|shopify/)) {
      try {
        const defOrd = { orders: 0, revenue: 0, avgValue: 0 };
        const [today, week, month, vendors] = await Promise.all([
          withTimeout(getShopifyOrders('today'), defOrd, 10000),
          withTimeout(getShopifyOrders('week'), defOrd, 10000),
          withTimeout(getShopifyOrders('month'), defOrd, 10000),
          withTimeout(getVendorStats(), [], 10000)
        ]);
        financialContext = `\n\nLIVE FINANCIAL DATA (real-time from Shopify + PostgreSQL):
- Revenue Today: ${formatCurrency(today.revenue)} (${today.orders} orders)
- Revenue This Week: ${formatCurrency(week.revenue)} (${week.orders} orders)
- Revenue This Month: ${formatCurrency(month.revenue)} (${month.orders} orders)
- Avg Order Value (month): ${formatCurrency(month.avgValue)}
- Vendor Catalog Summary: ${vendors.map(v => `${v.vendor}: ${v.total} products, ${v.imported} imported`).join('; ')}`;
      } catch { /* continue without context */ }
    }

    const systemPrompt = `You are the CFO's financial AI advisor for Designer Wallcoverings, a luxury wallcovering company.

Your role is to analyze revenue trends, order patterns, and financial metrics. The company sells luxury wallcoverings through Shopify with 145K+ products across multiple premium vendors (Arte, Thibaut, Schumacher, Koroseal, and more).

You have direct access to live financial data from Shopify and PostgreSQL databases.

Focus on:
- Revenue analysis and forecasting
- Order pattern insights
- Vendor catalog ROI (products imported vs total catalog)
- Cost optimization recommendations
- Cash flow considerations
- Financial risk identification
- Margin analysis

Be concise, data-driven, and actionable. Present numbers clearly. Flag any concerns proactively.${financialContext}`;

    const chatMessages = req.session.chatHistory.map(m => ({
      role: m.role as 'user' | 'assistant',
      content: m.content
    }));

    const response = await anthropic.messages.create({
      model: 'claude-opus-4-8',
      max_tokens: 2048,
      system: systemPrompt,
      messages: chatMessages
    });

    const reply = response.content[0].type === 'text'
      ? response.content[0].text
      : 'I encountered an error processing your request.';

    req.session.chatHistory.push({ role: 'assistant', content: reply });

    // Keep history manageable
    if (req.session.chatHistory.length > 20) {
      req.session.chatHistory = req.session.chatHistory.slice(-20);
    }

    res.json({ response: reply });
  } catch (err: any) {
    console.error('[CFO Chat Error]', err.message);
    res.status(500).json({
      error: 'Chat processing failed',
      response: 'I apologize, but I encountered an error. Please try again.'
    });
  }
});

// ─── Helper: Get yesterday's orders ─────────────────────────────────────
async function getYesterdayOrders(): Promise<{ orders: number; revenue: number; avgValue: number }> {
  const now = new Date();
  const yesterdayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
  const yesterdayEnd = new Date(now.getFullYear(), now.getMonth(), now.getDate());

  const data = await shopifyGraphQL(`{
    orders(first: 250, query: "created_at:>='${yesterdayStart.toISOString()}' AND created_at:<'${yesterdayEnd.toISOString()}'") {
      edges { node { id totalPriceSet { shopMoney { amount } } createdAt } }
    }
  }`);

  const edges = data?.data?.orders?.edges || [];
  const revenue = edges.reduce((s: number, e: any) => s + parseFloat(e.node.totalPriceSet?.shopMoney?.amount || '0'), 0);
  return {
    orders: edges.length,
    revenue: Math.round(revenue * 100) / 100,
    avgValue: edges.length ? Math.round(revenue / edges.length * 100) / 100 : 0
  };
}

// ─── Helper: Get recent orders for table ────────────────────────────────
async function getRecentOrders(): Promise<any[]> {
  const data = await shopifyGraphQL(`{
    orders(first: 20, reverse: true) {
      edges {
        node {
          name
          createdAt
          totalPriceSet { shopMoney { amount } }
          displayFinancialStatus
          displayFulfillmentStatus
        }
      }
    }
  }`);

  return (data?.data?.orders?.edges || []).map((e: any) => ({
    name: e.node.name,
    createdAt: e.node.createdAt,
    amount: e.node.totalPriceSet?.shopMoney?.amount || '0',
    financialStatus: e.node.displayFinancialStatus || 'UNKNOWN',
    fulfillmentStatus: e.node.displayFulfillmentStatus || 'UNFULFILLED'
  }));
}

// ─── Helper: Auto-detect action items ───────────────────────────────────
function detectActionItems(todayRev: number, weekAvg: number, vendorStats: Array<{ vendor: string; total: number; imported: number; withImages: number }>) {
  // Revenue below average check
  if (weekAvg > 0 && todayRev < weekAvg * 0.5) {
    actionStore.add(
      'Revenue Monitor',
      'warning',
      'Revenue Below Average',
      `Today's revenue (${formatCurrency(todayRev)}) is below 50% of the weekly average (${formatCurrency(weekAvg)}). Consider promotional actions or investigate drop in traffic.`
    );
  }

  // Low vendor import rate check
  for (const v of vendorStats) {
    if (v.total > 50) {
      const pct = v.total > 0 ? (v.imported / v.total * 100) : 0;
      if (pct < 10) {
        actionStore.add(
          'Vendor Investment',
          'warning',
          `Low Vendor Import Rate: ${v.vendor}`,
          `${v.vendor} has only ${pct.toFixed(1)}% of products imported to Shopify (${v.imported}/${v.total}). Potential untapped revenue opportunity.`
        );
      }
    }
  }
}

// ─── Start Server ───────────────────────────────────────────────────────
app.listen(PORT, '0.0.0.0', () => {
  console.log('');
  console.log('💰 CFO Executive Dashboard');
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
  console.log(`🌍 External: http://${SERVER_IP}:${PORT}`);
  console.log(`🏠 Local: http://localhost:${PORT}`);
  console.log('');
  console.log('✅ CFO Dashboard ready - All financial data is LIVE from Shopify + PostgreSQL');
});