diff --git a/services/arbiter-3.0/src/routes/admin/financials.js b/services/arbiter-3.0/src/routes/admin/financials.js new file mode 100644 index 0000000..7918ac3 --- /dev/null +++ b/services/arbiter-3.0/src/routes/admin/financials.js @@ -0,0 +1,86 @@ +const express = require('express'); +const router = express.Router(); +const db = require('../../database'); +const { TIER_INFO } = require('./constants'); + +router.get('/', async (req, res) => { + try { + // Query 1: Global Health Metrics + const { rows: healthData } = await db.query(` + SELECT + COUNT(*) FILTER (WHERE status = 'active' AND tier_level != 499) as active_subs, + SUM(mrr_value) FILTER (WHERE status = 'active') as recognized_mrr, + COUNT(*) FILTER (WHERE status = 'grace_period') as at_risk_subs, + SUM(mrr_value) FILTER (WHERE status = 'grace_period') as at_risk_mrr, + COUNT(*) FILTER (WHERE tier_level = 499 AND status IN ('active', 'lifetime')) as lifetime_subs + FROM subscriptions; + `); + + const metrics = { + activeSubs: parseInt(healthData[0].active_subs || 0), + recognizedMrr: parseFloat(healthData[0].recognized_mrr || 0), + atRiskSubs: parseInt(healthData[0].at_risk_subs || 0), + atRiskMrr: parseFloat(healthData[0].at_risk_mrr || 0), + lifetimeSubs: parseInt(healthData[0].lifetime_subs || 0) + }; + + metrics.lifetimeRevenue = metrics.lifetimeSubs * 499.00; + metrics.arpu = metrics.activeSubs > 0 ? (metrics.recognizedMrr / metrics.activeSubs).toFixed(2) : 0; + metrics.arr = (metrics.recognizedMrr * 12).toFixed(2); + + // Query 2: Tier Breakdown + const { rows: tierData } = await db.query(` + SELECT tier_level, status, COUNT(*) as count, SUM(mrr_value) as total_mrr + FROM subscriptions + WHERE status IN ('active', 'grace_period', 'lifetime') + GROUP BY tier_level, status; + `); + + // Process paths (Fire vs Frost) + const paths = { + fire: { mrr: 0, subs: 0 }, + frost: { mrr: 0, subs: 0 }, + universal: { mrr: 0, subs: 0 } + }; + + const tierBreakdown = {}; + + tierData.forEach(row => { + const tLevel = row.tier_level; + const info = TIER_INFO[tLevel] || { name: 'Unknown', path: 'universal' }; + const mrr = parseFloat(row.total_mrr || 0); + const count = parseInt(row.count || 0); + + // Tally active MRR for the Path comparison (excluding grace period from guaranteed MRR) + if (row.status === 'active') { + if (paths[info.path]) { + paths[info.path].mrr += mrr; + paths[info.path].subs += count; + } + } + + // Build detailed table data + if (!tierBreakdown[tLevel]) { + tierBreakdown[tLevel] = { ...info, activeCount: 0, graceCount: 0, totalMrr: 0 }; + } + + if (row.status === 'active' || row.status === 'lifetime') tierBreakdown[tLevel].activeCount += count; + if (row.status === 'grace_period') tierBreakdown[tLevel].graceCount += count; + if (row.status === 'active') tierBreakdown[tLevel].totalMrr += mrr; + }); + + res.render('admin/financials/index', { + title: 'Financials & Analytics', + metrics, + paths, + tierBreakdown, + TIER_INFO + }); + + } catch (error) { + console.error("Financials Error:", error); + res.status(500).send("Error loading financial data."); + } +}); + +module.exports = router; diff --git a/services/arbiter-3.0/src/routes/admin/index.js b/services/arbiter-3.0/src/routes/admin/index.js index fca2425..1ed060c 100644 --- a/services/arbiter-3.0/src/routes/admin/index.js +++ b/services/arbiter-3.0/src/routes/admin/index.js @@ -2,10 +2,10 @@ const express = require('express'); const router = express.Router(); const { requireTrinityAccess } = require('./middleware'); -// Sub-routers (We will populate these as we go) +// Sub-routers const playersRouter = require('./players'); const serversRouter = require('./servers'); -// const financialsRouter = require('./financials'); +const financialsRouter = require('./financials'); router.use(requireTrinityAccess); @@ -19,5 +19,6 @@ router.get('/dashboard', (req, res) => { router.use('/players', playersRouter); router.use('/servers', serversRouter); +router.use('/financials', financialsRouter); module.exports = router; diff --git a/services/arbiter-3.0/src/views/admin/financials/index.ejs b/services/arbiter-3.0/src/views/admin/financials/index.ejs new file mode 100644 index 0000000..11ecb3d --- /dev/null +++ b/services/arbiter-3.0/src/views/admin/financials/index.ejs @@ -0,0 +1,129 @@ +<%- include('../../layout', { body: ` +
+
+

Revenue Analytics

+

Real-time MRR and subscriber intelligence

+
+ +
+ +
+
+

Recognized MRR

+
+ $<%= metrics.recognizedMrr.toFixed(2) %> + /mo +
+

Annual Run Rate: $<%= metrics.arr %>

+
+ +
+

At-Risk MRR (Grace Period)

+
+ $<%= metrics.atRiskMrr.toFixed(2) %> +
+

<%= metrics.atRiskSubs %> subscribers pending recovery

+
+ +
+

Active Recurring Subs

+
+ <%= metrics.activeSubs %> +
+

ARPU: $<%= metrics.arpu %>

+
+ +
+

Lifetime Revenue (Sovereign)

+
+ $<%= metrics.lifetimeRevenue.toFixed(2) %> +
+

<%= metrics.lifetimeSubs %> Sovereign Members

+
+
+ +
+
+

Path Dominance: Fire vs Frost

+
+
+ <% + const totalPathMrr = paths.fire.mrr + paths.frost.mrr; + const firePct = totalPathMrr > 0 ? (paths.fire.mrr / totalPathMrr) * 100 : 50; + const frostPct = totalPathMrr > 0 ? (paths.frost.mrr / totalPathMrr) * 100 : 50; + %> +
+
+ <%= firePct > 10 ? firePct.toFixed(1) + '%' : '' %> +
+
+ <%= frostPct > 10 ? frostPct.toFixed(1) + '%' : '' %> +
+
+ +
+
+

🔥 Fire Path

+

$<%= paths.fire.mrr.toFixed(2) %>

+

<%= paths.fire.subs %> Active Subs

+
+
+

❄️ Frost Path

+

$<%= paths.frost.mrr.toFixed(2) %>

+

<%= paths.frost.subs %> Active Subs

+
+
+
+
+ +
+
+

Tier Breakdown

+
+
+ + + + + + + + + + + + <% Object.keys(tierBreakdown).sort((a,b) => a - b).forEach(tierKey => { + const tier = tierBreakdown[tierKey]; + const pctOfTotal = metrics.recognizedMrr > 0 ? ((tier.totalMrr / metrics.recognizedMrr) * 100).toFixed(1) : 0; + %> + + + + + + + + <% }) %> + +
Tier NameActive SubsAt-Risk (Grace)Recognized MRR% of Total MRR
+ + <% if(tier.path === 'fire') { %>🔥<% } %> + <% if(tier.path === 'frost') { %>❄️<% } %> + <% if(tier.path === 'universal') { %>⚡<% } %> + <%= tier.name %> + + <%= tier.activeCount %><%= tier.graceCount > 0 ? tier.graceCount : '-' %> + $<%= tier.totalMrr.toFixed(2) %> + +
+ <%= pctOfTotal %>% +
+
+
+
+
+
+
+`}) %>