perf: parallelize BI queries to fix 504 timeout
- fetchBIData: 12 sequential queries → 1 Promise.all (14 parallel) - fetchBIStrategic: 6 sequential queries → 4 parallel sections (cohorts, expansion, cross-sell, maturity), each with internal parallelism via Promise.all - Total response time ≈ slowest single query instead of sum of all Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,184 +10,151 @@ async function fetchBIData(dataInicio, dataFim, getAgenteName = null) {
|
|||||||
try {
|
try {
|
||||||
const { prevStartStr, prevEndStr } = calcPrevPeriod(dataInicio, dataFim);
|
const { prevStartStr, prevEndStr } = calcPrevPeriod(dataInicio, dataFim);
|
||||||
|
|
||||||
// 1. BRL→USD KPIs
|
// Run all 12 queries in parallel
|
||||||
const [kpiBrlUsd] = await conn.execute(`
|
const [
|
||||||
SELECT
|
[kpiBrlUsd], [kpiUsdBrl], [kpiUsdUsd], [uniqueClients],
|
||||||
COUNT(*) as qtd,
|
[prevBrlUsd], [prevUsdBrl], [prevUsdUsd],
|
||||||
ROUND(COALESCE(SUM(amount_usd), 0), 2) as vol_usd,
|
[trendBrlUsd], [trendUsdBrl], [trendUsdUsd],
|
||||||
ROUND(COALESCE(SUM(amount_brl), 0), 2) as vol_brl,
|
[topClients], [retention], [clientsAtRisk], [agentRanking]
|
||||||
ROUND(COALESCE(SUM((exchange_rate - ptax) / exchange_rate * amount_usd), 0), 2) as spread_revenue,
|
] = await Promise.all([
|
||||||
ROUND(COALESCE(AVG((exchange_rate - ptax) / exchange_rate * 100), 0), 2) as avg_spread_pct,
|
// 1. BRL→USD KPIs
|
||||||
COUNT(DISTINCT id_conta) as clientes
|
conn.execute(`
|
||||||
FROM br_transaction_to_usa
|
SELECT COUNT(*) as qtd,
|
||||||
WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
ROUND(COALESCE(SUM(amount_usd), 0), 2) as vol_usd,
|
||||||
`, [dataInicio, dataFim]);
|
ROUND(COALESCE(SUM(amount_brl), 0), 2) as vol_brl,
|
||||||
|
ROUND(COALESCE(SUM((exchange_rate - ptax) / exchange_rate * amount_usd), 0), 2) as spread_revenue,
|
||||||
// 2. USD→BRL KPIs
|
ROUND(COALESCE(AVG((exchange_rate - ptax) / exchange_rate * 100), 0), 2) as avg_spread_pct,
|
||||||
const [kpiUsdBrl] = await conn.execute(`
|
COUNT(DISTINCT id_conta) as clientes
|
||||||
SELECT
|
FROM br_transaction_to_usa
|
||||||
COUNT(*) as qtd,
|
|
||||||
ROUND(COALESCE(SUM(valor), 0), 2) as vol_usd,
|
|
||||||
ROUND(COALESCE(SUM(valor_sol), 0), 2) as vol_brl,
|
|
||||||
ROUND(COALESCE(SUM((ptax - cotacao) / ptax * valor), 0), 2) as spread_revenue,
|
|
||||||
ROUND(COALESCE(AVG(CASE WHEN cotacao > 0 THEN (ptax - cotacao) / ptax * 100 ELSE 0 END), 0), 2) as avg_spread_pct,
|
|
||||||
COUNT(DISTINCT id_conta) as clientes
|
|
||||||
FROM pagamento_br
|
|
||||||
WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
|
||||||
AND cotacao IS NOT NULL AND cotacao > 0
|
|
||||||
AND (pgto IS NULL OR pgto != 'balance')
|
|
||||||
`, [dataInicio, dataFim]);
|
|
||||||
|
|
||||||
// 3. USD→USD KPIs
|
|
||||||
const [kpiUsdUsd] = await conn.execute(`
|
|
||||||
SELECT
|
|
||||||
COUNT(*) as qtd,
|
|
||||||
ROUND(COALESCE(SUM(valor), 0), 2) as vol_usd,
|
|
||||||
COUNT(DISTINCT id_conta) as clientes
|
|
||||||
FROM pagamento_br
|
|
||||||
WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
|
||||||
AND (cotacao IS NULL OR cotacao = 0 OR pgto = 'balance')
|
|
||||||
`, [dataInicio, dataFim]);
|
|
||||||
|
|
||||||
// 4. Unique active clients across all flows
|
|
||||||
const [uniqueClients] = await conn.execute(`
|
|
||||||
SELECT COUNT(DISTINCT id_conta) as total FROM (
|
|
||||||
SELECT id_conta FROM br_transaction_to_usa
|
|
||||||
WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
UNION
|
`, [dataInicio, dataFim]),
|
||||||
SELECT id_conta FROM pagamento_br
|
// 2. USD→BRL KPIs
|
||||||
|
conn.execute(`
|
||||||
|
SELECT COUNT(*) as qtd,
|
||||||
|
ROUND(COALESCE(SUM(valor), 0), 2) as vol_usd,
|
||||||
|
ROUND(COALESCE(SUM(valor_sol), 0), 2) as vol_brl,
|
||||||
|
ROUND(COALESCE(SUM((ptax - cotacao) / ptax * valor), 0), 2) as spread_revenue,
|
||||||
|
ROUND(COALESCE(AVG(CASE WHEN cotacao > 0 THEN (ptax - cotacao) / ptax * 100 ELSE 0 END), 0), 2) as avg_spread_pct,
|
||||||
|
COUNT(DISTINCT id_conta) as clientes
|
||||||
|
FROM pagamento_br
|
||||||
WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
) all_clients
|
AND cotacao IS NOT NULL AND cotacao > 0
|
||||||
`, [dataInicio, dataFim, dataInicio, dataFim]);
|
AND (pgto IS NULL OR pgto != 'balance')
|
||||||
|
`, [dataInicio, dataFim]),
|
||||||
// 5. Previous period totals for comparison
|
// 3. USD→USD KPIs
|
||||||
const [prevBrlUsd] = await conn.execute(`
|
conn.execute(`
|
||||||
SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(amount_usd),0),2) as vol_usd,
|
SELECT COUNT(*) as qtd,
|
||||||
ROUND(COALESCE(SUM((exchange_rate - ptax) / exchange_rate * amount_usd),0),2) as spread_revenue
|
ROUND(COALESCE(SUM(valor), 0), 2) as vol_usd,
|
||||||
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
COUNT(DISTINCT id_conta) as clientes
|
||||||
`, [prevStartStr, prevEndStr]);
|
FROM pagamento_br
|
||||||
const [prevUsdBrl] = await conn.execute(`
|
WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(valor),0),2) as vol_usd,
|
AND (cotacao IS NULL OR cotacao = 0 OR pgto = 'balance')
|
||||||
ROUND(COALESCE(SUM((ptax - cotacao) / ptax * valor),0),2) as spread_revenue
|
`, [dataInicio, dataFim]),
|
||||||
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
// 4. Unique active clients
|
||||||
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
conn.execute(`
|
||||||
`, [prevStartStr, prevEndStr]);
|
SELECT COUNT(DISTINCT id_conta) as total FROM (
|
||||||
const [prevUsdUsd] = await conn.execute(`
|
SELECT id_conta FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(valor),0),2) as vol_usd
|
UNION
|
||||||
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
SELECT id_conta FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
AND (cotacao IS NULL OR cotacao = 0 OR pgto = 'balance')
|
) all_clients
|
||||||
`, [prevStartStr, prevEndStr]);
|
`, [dataInicio, dataFim, dataInicio, dataFim]),
|
||||||
|
// 5. Previous period
|
||||||
// 6. BRL→USD daily trend with spread
|
conn.execute(`
|
||||||
const [trendBrlUsd] = await conn.execute(`
|
SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(amount_usd),0),2) as vol_usd,
|
||||||
SELECT DATE(created_at) as dia, COUNT(*) as qtd,
|
ROUND(COALESCE(SUM((exchange_rate - ptax) / exchange_rate * amount_usd),0),2) as spread_revenue
|
||||||
ROUND(SUM(amount_usd), 2) as vol_usd,
|
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
ROUND(AVG((exchange_rate - ptax) / exchange_rate * 100), 2) as avg_spread
|
`, [prevStartStr, prevEndStr]),
|
||||||
FROM br_transaction_to_usa
|
conn.execute(`
|
||||||
WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(valor),0),2) as vol_usd,
|
||||||
GROUP BY DATE(created_at) ORDER BY dia
|
ROUND(COALESCE(SUM((ptax - cotacao) / ptax * valor),0),2) as spread_revenue
|
||||||
`, [dataInicio, dataFim]);
|
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
|
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
||||||
// 7. USD→BRL daily trend with spread
|
`, [prevStartStr, prevEndStr]),
|
||||||
const [trendUsdBrl] = await conn.execute(`
|
conn.execute(`
|
||||||
SELECT DATE(created_at) as dia, COUNT(*) as qtd,
|
SELECT COUNT(*) as qtd, ROUND(COALESCE(SUM(valor),0),2) as vol_usd
|
||||||
ROUND(SUM(valor), 2) as vol_usd,
|
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
ROUND(AVG(CASE WHEN cotacao > 0 THEN (ptax - cotacao) / ptax * 100 ELSE 0 END), 2) as avg_spread
|
AND (cotacao IS NULL OR cotacao = 0 OR pgto = 'balance')
|
||||||
FROM pagamento_br
|
`, [prevStartStr, prevEndStr]),
|
||||||
WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
// 6-8. Trends
|
||||||
AND cotacao IS NOT NULL AND cotacao > 0
|
conn.execute(`
|
||||||
AND (pgto IS NULL OR pgto != 'balance')
|
SELECT DATE(created_at) as dia, COUNT(*) as qtd,
|
||||||
GROUP BY DATE(created_at) ORDER BY dia
|
ROUND(SUM(amount_usd), 2) as vol_usd,
|
||||||
`, [dataInicio, dataFim]);
|
ROUND(AVG((exchange_rate - ptax) / exchange_rate * 100), 2) as avg_spread
|
||||||
|
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
// 8. USD→USD daily trend
|
GROUP BY DATE(created_at) ORDER BY dia
|
||||||
const [trendUsdUsd] = await conn.execute(`
|
`, [dataInicio, dataFim]),
|
||||||
SELECT DATE(created_at) as dia, COUNT(*) as qtd,
|
conn.execute(`
|
||||||
ROUND(SUM(valor), 2) as vol_usd
|
SELECT DATE(created_at) as dia, COUNT(*) as qtd,
|
||||||
FROM pagamento_br
|
ROUND(SUM(valor), 2) as vol_usd,
|
||||||
WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
ROUND(AVG(CASE WHEN cotacao > 0 THEN (ptax - cotacao) / ptax * 100 ELSE 0 END), 2) as avg_spread
|
||||||
AND (cotacao IS NULL OR cotacao = 0 OR pgto = 'balance')
|
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
GROUP BY DATE(created_at) ORDER BY dia
|
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
||||||
`, [dataInicio, dataFim]);
|
GROUP BY DATE(created_at) ORDER BY dia
|
||||||
|
`, [dataInicio, dataFim]),
|
||||||
// 9. Top 10 clients by volume
|
conn.execute(`
|
||||||
const [topClients] = await conn.execute(`
|
SELECT DATE(created_at) as dia, COUNT(*) as qtd, ROUND(SUM(valor), 2) as vol_usd
|
||||||
SELECT nome, SUM(vol) as total_usd, SUM(qtd) as total_qtd FROM (
|
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
SELECT c.nome, SUM(t.amount_usd) as vol, COUNT(*) as qtd
|
AND (cotacao IS NULL OR cotacao = 0 OR pgto = 'balance')
|
||||||
FROM br_transaction_to_usa t
|
GROUP BY DATE(created_at) ORDER BY dia
|
||||||
INNER JOIN conta c ON c.id_conta = t.id_conta
|
`, [dataInicio, dataFim]),
|
||||||
WHERE DATE(t.created_at) >= ? AND DATE(t.created_at) <= ?
|
// 9. Top clients
|
||||||
GROUP BY c.nome
|
conn.execute(`
|
||||||
UNION ALL
|
SELECT nome, SUM(vol) as total_usd, SUM(qtd) as total_qtd FROM (
|
||||||
SELECT c.nome, SUM(p.valor) as vol, COUNT(*) as qtd
|
SELECT c.nome, SUM(t.amount_usd) as vol, COUNT(*) as qtd
|
||||||
FROM pagamento_br p
|
FROM br_transaction_to_usa t INNER JOIN conta c ON c.id_conta = t.id_conta
|
||||||
INNER JOIN conta c ON c.id_conta = p.id_conta
|
WHERE DATE(t.created_at) >= ? AND DATE(t.created_at) <= ? GROUP BY c.nome
|
||||||
WHERE DATE(p.created_at) >= ? AND DATE(p.created_at) <= ?
|
UNION ALL
|
||||||
GROUP BY c.nome
|
SELECT c.nome, SUM(p.valor) as vol, COUNT(*) as qtd
|
||||||
) combined
|
FROM pagamento_br p INNER JOIN conta c ON c.id_conta = p.id_conta
|
||||||
GROUP BY nome ORDER BY total_usd DESC LIMIT 10
|
WHERE DATE(p.created_at) >= ? AND DATE(p.created_at) <= ? GROUP BY c.nome
|
||||||
`, [dataInicio, dataFim, dataInicio, dataFim]);
|
) combined GROUP BY nome ORDER BY total_usd DESC LIMIT 10
|
||||||
|
`, [dataInicio, dataFim, dataInicio, dataFim]),
|
||||||
// 10. Client retention
|
// 10. Retention
|
||||||
const [retention] = await conn.execute(`
|
conn.execute(`
|
||||||
SELECT
|
SELECT COUNT(DISTINCT prev.id_conta) as prev_clients,
|
||||||
COUNT(DISTINCT prev.id_conta) as prev_clients,
|
COUNT(DISTINCT CASE WHEN curr.id_conta IS NOT NULL THEN prev.id_conta END) as retained
|
||||||
COUNT(DISTINCT CASE WHEN curr.id_conta IS NOT NULL THEN prev.id_conta END) as retained
|
FROM (
|
||||||
FROM (
|
SELECT DISTINCT id_conta FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
SELECT DISTINCT id_conta FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
UNION
|
||||||
UNION
|
SELECT DISTINCT id_conta FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
SELECT DISTINCT id_conta FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
) prev LEFT JOIN (
|
||||||
) prev
|
SELECT DISTINCT id_conta FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
LEFT JOIN (
|
UNION
|
||||||
SELECT DISTINCT id_conta FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
SELECT DISTINCT id_conta FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
UNION
|
) curr ON prev.id_conta = curr.id_conta
|
||||||
SELECT DISTINCT id_conta FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
`, [prevStartStr, prevEndStr, prevStartStr, prevEndStr, dataInicio, dataFim, dataInicio, dataFim]),
|
||||||
) curr ON prev.id_conta = curr.id_conta
|
// 11. Clients at risk
|
||||||
`, [prevStartStr, prevEndStr, prevStartStr, prevEndStr, dataInicio, dataFim, dataInicio, dataFim]);
|
conn.execute(`
|
||||||
|
SELECT nome, MAX(last_op) as last_op, SUM(vol) as total_usd, SUM(qtd) as total_qtd,
|
||||||
// 11. Clients at risk
|
DATEDIFF(CURDATE(), MAX(last_op)) as days_inactive
|
||||||
const [clientsAtRisk] = await conn.execute(`
|
FROM (
|
||||||
SELECT nome, MAX(last_op) as last_op, SUM(vol) as total_usd, SUM(qtd) as total_qtd,
|
SELECT c.nome, MAX(t.created_at) as last_op, SUM(t.amount_usd) as vol, COUNT(*) as qtd
|
||||||
DATEDIFF(CURDATE(), MAX(last_op)) as days_inactive
|
FROM br_transaction_to_usa t INNER JOIN conta c ON c.id_conta = t.id_conta GROUP BY c.nome
|
||||||
FROM (
|
UNION ALL
|
||||||
SELECT c.nome, MAX(t.created_at) as last_op, SUM(t.amount_usd) as vol, COUNT(*) as qtd
|
SELECT c.nome, MAX(p.created_at) as last_op, SUM(p.valor) as vol, COUNT(*) as qtd
|
||||||
FROM br_transaction_to_usa t
|
FROM pagamento_br p INNER JOIN conta c ON c.id_conta = p.id_conta GROUP BY c.nome
|
||||||
INNER JOIN conta c ON c.id_conta = t.id_conta
|
) combined GROUP BY nome HAVING MAX(last_op) < CURDATE() ORDER BY total_usd DESC LIMIT 20
|
||||||
GROUP BY c.nome
|
`),
|
||||||
UNION ALL
|
// 12. Agent ranking
|
||||||
SELECT c.nome, MAX(p.created_at) as last_op, SUM(p.valor) as vol, COUNT(*) as qtd
|
conn.execute(`
|
||||||
FROM pagamento_br p
|
SELECT agente_id, SUM(vol) as total_usd, SUM(qtd) as total_qtd,
|
||||||
INNER JOIN conta c ON c.id_conta = p.id_conta
|
ROUND(SUM(spread_rev), 2) as total_spread, COUNT(DISTINCT client_id) as clientes
|
||||||
GROUP BY c.nome
|
FROM (
|
||||||
) combined
|
SELECT ac.agente_id, t.id_conta as client_id, SUM(t.amount_usd) as vol, COUNT(*) as qtd,
|
||||||
GROUP BY nome
|
SUM((t.exchange_rate - t.ptax) / t.exchange_rate * t.amount_usd) as spread_rev
|
||||||
HAVING MAX(last_op) < CURDATE()
|
FROM br_transaction_to_usa t INNER JOIN ag_contas ac ON ac.conta_id = t.id_conta
|
||||||
ORDER BY total_usd DESC LIMIT 20
|
WHERE DATE(t.created_at) >= ? AND DATE(t.created_at) <= ?
|
||||||
`);
|
GROUP BY ac.agente_id, t.id_conta
|
||||||
|
UNION ALL
|
||||||
// 12. Agent ranking with spread revenue
|
SELECT ac.agente_id, p.id_conta as client_id, SUM(p.valor) as vol, COUNT(*) as qtd,
|
||||||
const [agentRanking] = await conn.execute(`
|
SUM((p.ptax - p.cotacao) / p.ptax * p.valor) as spread_rev
|
||||||
SELECT agente_id, SUM(vol) as total_usd, SUM(qtd) as total_qtd,
|
FROM pagamento_br p INNER JOIN ag_contas ac ON ac.conta_id = p.id_conta
|
||||||
ROUND(SUM(spread_rev), 2) as total_spread, COUNT(DISTINCT client_id) as clientes
|
WHERE DATE(p.created_at) >= ? AND DATE(p.created_at) <= ?
|
||||||
FROM (
|
AND p.cotacao IS NOT NULL AND p.cotacao > 0 AND (p.pgto IS NULL OR p.pgto != 'balance')
|
||||||
SELECT ac.agente_id, t.id_conta as client_id, SUM(t.amount_usd) as vol, COUNT(*) as qtd,
|
GROUP BY ac.agente_id, p.id_conta
|
||||||
SUM((t.exchange_rate - t.ptax) / t.exchange_rate * t.amount_usd) as spread_rev
|
) combined GROUP BY agente_id ORDER BY total_usd DESC LIMIT 10
|
||||||
FROM br_transaction_to_usa t
|
`, [dataInicio, dataFim, dataInicio, dataFim])
|
||||||
INNER JOIN ag_contas ac ON ac.conta_id = t.id_conta
|
]);
|
||||||
WHERE DATE(t.created_at) >= ? AND DATE(t.created_at) <= ?
|
|
||||||
GROUP BY ac.agente_id, t.id_conta
|
|
||||||
UNION ALL
|
|
||||||
SELECT ac.agente_id, p.id_conta as client_id, SUM(p.valor) as vol, COUNT(*) as qtd,
|
|
||||||
SUM((p.ptax - p.cotacao) / p.ptax * p.valor) as spread_rev
|
|
||||||
FROM pagamento_br p
|
|
||||||
INNER JOIN ag_contas ac ON ac.conta_id = p.id_conta
|
|
||||||
WHERE DATE(p.created_at) >= ? AND DATE(p.created_at) <= ?
|
|
||||||
AND p.cotacao IS NOT NULL AND p.cotacao > 0
|
|
||||||
AND (p.pgto IS NULL OR p.pgto != 'balance')
|
|
||||||
GROUP BY ac.agente_id, p.id_conta
|
|
||||||
) combined
|
|
||||||
GROUP BY agente_id ORDER BY total_usd DESC LIMIT 10
|
|
||||||
`, [dataInicio, dataFim, dataInicio, dataFim]);
|
|
||||||
|
|
||||||
// Resolve agent names
|
// Resolve agent names
|
||||||
const agents = agentRanking.map((r, i) => {
|
const agents = agentRanking.map((r, i) => {
|
||||||
@@ -444,8 +411,23 @@ async function fetchBIStrategic(dataInicio, dataFim) {
|
|||||||
try {
|
try {
|
||||||
const { prevStartStr, prevEndStr } = calcPrevPeriod(dataInicio, dataFim);
|
const { prevStartStr, prevEndStr } = calcPrevPeriod(dataInicio, dataFim);
|
||||||
|
|
||||||
// === 1. COHORT RETENTION ===
|
// Run all 4 sections in parallel — each is independent
|
||||||
const [cohortClients] = await conn.execute(`
|
const [cohorts, expansion, crossSell, maturity] = await Promise.all([
|
||||||
|
_fetchCohorts(conn),
|
||||||
|
_fetchExpansion(conn, dataInicio, dataFim, prevStartStr, prevEndStr),
|
||||||
|
_fetchCrossSell(conn, dataInicio, dataFim),
|
||||||
|
_fetchMaturity(conn, dataInicio, dataFim, prevStartStr, prevEndStr)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { cohorts, expansion, crossSell, maturity };
|
||||||
|
} finally {
|
||||||
|
conn.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _fetchCohorts(conn) {
|
||||||
|
const [[cohortClients], [activeMonths]] = await Promise.all([
|
||||||
|
conn.execute(`
|
||||||
SELECT id_conta, DATE_FORMAT(MIN(first_op), '%Y-%m') as cohort_month FROM (
|
SELECT id_conta, DATE_FORMAT(MIN(first_op), '%Y-%m') as cohort_month FROM (
|
||||||
SELECT id_conta, MIN(created_at) as first_op FROM br_transaction_to_usa GROUP BY id_conta
|
SELECT id_conta, MIN(created_at) as first_op FROM br_transaction_to_usa GROUP BY id_conta
|
||||||
UNION ALL
|
UNION ALL
|
||||||
@@ -453,8 +435,8 @@ async function fetchBIStrategic(dataInicio, dataFim) {
|
|||||||
WHERE cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
WHERE cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
||||||
GROUP BY id_conta
|
GROUP BY id_conta
|
||||||
) f GROUP BY id_conta
|
) f GROUP BY id_conta
|
||||||
`);
|
`),
|
||||||
const [activeMonths] = await conn.execute(`
|
conn.execute(`
|
||||||
SELECT id_conta, active_month FROM (
|
SELECT id_conta, active_month FROM (
|
||||||
SELECT id_conta, DATE_FORMAT(created_at, '%Y-%m') as active_month
|
SELECT id_conta, DATE_FORMAT(created_at, '%Y-%m') as active_month
|
||||||
FROM br_transaction_to_usa GROUP BY id_conta, DATE_FORMAT(created_at, '%Y-%m')
|
FROM br_transaction_to_usa GROUP BY id_conta, DATE_FORMAT(created_at, '%Y-%m')
|
||||||
@@ -463,50 +445,53 @@ async function fetchBIStrategic(dataInicio, dataFim) {
|
|||||||
FROM pagamento_br WHERE cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
FROM pagamento_br WHERE cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
||||||
GROUP BY id_conta, DATE_FORMAT(created_at, '%Y-%m')
|
GROUP BY id_conta, DATE_FORMAT(created_at, '%Y-%m')
|
||||||
) m
|
) m
|
||||||
`);
|
`)
|
||||||
|
]);
|
||||||
|
|
||||||
const clientCohort = {};
|
const clientCohort = {};
|
||||||
cohortClients.forEach(r => { clientCohort[r.id_conta] = r.cohort_month; });
|
cohortClients.forEach(r => { clientCohort[r.id_conta] = r.cohort_month; });
|
||||||
const clientMonths = {};
|
const clientMonths = {};
|
||||||
activeMonths.forEach(r => {
|
activeMonths.forEach(r => {
|
||||||
if (!clientMonths[r.id_conta]) clientMonths[r.id_conta] = new Set();
|
if (!clientMonths[r.id_conta]) clientMonths[r.id_conta] = new Set();
|
||||||
clientMonths[r.id_conta].add(r.active_month);
|
clientMonths[r.id_conta].add(r.active_month);
|
||||||
});
|
});
|
||||||
const allMonths = [...new Set([...cohortClients.map(r => r.cohort_month), ...activeMonths.map(r => r.active_month)])].sort();
|
const allMonths = [...new Set([...cohortClients.map(r => r.cohort_month), ...activeMonths.map(r => r.active_month)])].sort();
|
||||||
|
|
||||||
const cohortMap = {};
|
const cohortMap = {};
|
||||||
cohortClients.forEach(r => {
|
cohortClients.forEach(r => {
|
||||||
const cm = r.cohort_month;
|
const cm = r.cohort_month;
|
||||||
if (!cohortMap[cm]) cohortMap[cm] = { size: 0, months: {} };
|
if (!cohortMap[cm]) cohortMap[cm] = { size: 0, months: {} };
|
||||||
cohortMap[cm].size++;
|
cohortMap[cm].size++;
|
||||||
});
|
});
|
||||||
Object.keys(clientCohort).forEach(clientId => {
|
Object.keys(clientCohort).forEach(clientId => {
|
||||||
const cm = clientCohort[clientId];
|
const cm = clientCohort[clientId];
|
||||||
const months = clientMonths[clientId] || new Set();
|
const months = clientMonths[clientId] || new Set();
|
||||||
const cmIdx = allMonths.indexOf(cm);
|
const cmIdx = allMonths.indexOf(cm);
|
||||||
months.forEach(am => {
|
months.forEach(am => {
|
||||||
const amIdx = allMonths.indexOf(am);
|
const amIdx = allMonths.indexOf(am);
|
||||||
const offset = amIdx - cmIdx;
|
const offset = amIdx - cmIdx;
|
||||||
if (offset >= 0) {
|
if (offset >= 0) {
|
||||||
if (!cohortMap[cm].months[offset]) cohortMap[cm].months[offset] = 0;
|
if (!cohortMap[cm].months[offset]) cohortMap[cm].months[offset] = 0;
|
||||||
cohortMap[cm].months[offset]++;
|
cohortMap[cm].months[offset]++;
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const cohortKeys = Object.keys(cohortMap).sort().slice(-12);
|
|
||||||
const cohorts = cohortKeys.map(cm => {
|
|
||||||
const c = cohortMap[cm];
|
|
||||||
const maxOff = allMonths.length - allMonths.indexOf(cm);
|
|
||||||
const retention = [];
|
|
||||||
for (let i = 0; i < Math.min(maxOff, 13); i++) {
|
|
||||||
retention.push(c.size > 0 ? Math.round((c.months[i] || 0) / c.size * 100) : 0);
|
|
||||||
}
|
}
|
||||||
return { month: cm, size: c.size, retention };
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// === 2. REVENUE EXPANSION / CONTRACTION ===
|
const cohortKeys = Object.keys(cohortMap).sort().slice(-12);
|
||||||
const [currRevenue] = await conn.execute(`
|
return cohortKeys.map(cm => {
|
||||||
|
const c = cohortMap[cm];
|
||||||
|
const maxOff = allMonths.length - allMonths.indexOf(cm);
|
||||||
|
const retention = [];
|
||||||
|
for (let i = 0; i < Math.min(maxOff, 13); i++) {
|
||||||
|
retention.push(c.size > 0 ? Math.round((c.months[i] || 0) / c.size * 100) : 0);
|
||||||
|
}
|
||||||
|
return { month: cm, size: c.size, retention };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _fetchExpansion(conn, dataInicio, dataFim, prevStartStr, prevEndStr) {
|
||||||
|
const [[currRevenue], [prevRevenue]] = await Promise.all([
|
||||||
|
conn.execute(`
|
||||||
SELECT id_conta, ROUND(SUM(revenue), 2) as revenue, ROUND(SUM(vol_usd), 2) as vol_usd FROM (
|
SELECT id_conta, ROUND(SUM(revenue), 2) as revenue, ROUND(SUM(vol_usd), 2) as vol_usd FROM (
|
||||||
SELECT id_conta, SUM((exchange_rate - ptax) / exchange_rate * amount_usd) as revenue, SUM(amount_usd) as vol_usd
|
SELECT id_conta, SUM((exchange_rate - ptax) / exchange_rate * amount_usd) as revenue, SUM(amount_usd) as vol_usd
|
||||||
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
@@ -517,8 +502,8 @@ async function fetchBIStrategic(dataInicio, dataFim) {
|
|||||||
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
||||||
GROUP BY id_conta
|
GROUP BY id_conta
|
||||||
) c GROUP BY id_conta
|
) c GROUP BY id_conta
|
||||||
`, [dataInicio, dataFim, dataInicio, dataFim]);
|
`, [dataInicio, dataFim, dataInicio, dataFim]),
|
||||||
const [prevRevenue] = await conn.execute(`
|
conn.execute(`
|
||||||
SELECT id_conta, ROUND(SUM(revenue), 2) as revenue, ROUND(SUM(vol_usd), 2) as vol_usd FROM (
|
SELECT id_conta, ROUND(SUM(revenue), 2) as revenue, ROUND(SUM(vol_usd), 2) as vol_usd FROM (
|
||||||
SELECT id_conta, SUM((exchange_rate - ptax) / exchange_rate * amount_usd) as revenue, SUM(amount_usd) as vol_usd
|
SELECT id_conta, SUM((exchange_rate - ptax) / exchange_rate * amount_usd) as revenue, SUM(amount_usd) as vol_usd
|
||||||
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
@@ -529,127 +514,128 @@ async function fetchBIStrategic(dataInicio, dataFim) {
|
|||||||
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
||||||
GROUP BY id_conta
|
GROUP BY id_conta
|
||||||
) p GROUP BY id_conta
|
) p GROUP BY id_conta
|
||||||
`, [prevStartStr, prevEndStr, prevStartStr, prevEndStr]);
|
`, [prevStartStr, prevEndStr, prevStartStr, prevEndStr])
|
||||||
|
]);
|
||||||
|
|
||||||
const currMap = {};
|
const currMap = {};
|
||||||
currRevenue.forEach(r => { currMap[r.id_conta] = { revenue: Number(r.revenue), vol_usd: Number(r.vol_usd) }; });
|
currRevenue.forEach(r => { currMap[r.id_conta] = { revenue: Number(r.revenue), vol_usd: Number(r.vol_usd) }; });
|
||||||
const prevMap = {};
|
const prevMap = {};
|
||||||
prevRevenue.forEach(r => { prevMap[r.id_conta] = { revenue: Number(r.revenue), vol_usd: Number(r.vol_usd) }; });
|
prevRevenue.forEach(r => { prevMap[r.id_conta] = { revenue: Number(r.revenue), vol_usd: Number(r.vol_usd) }; });
|
||||||
|
|
||||||
const allClientIds = new Set([...Object.keys(currMap), ...Object.keys(prevMap)]);
|
const allClientIds = new Set([...Object.keys(currMap), ...Object.keys(prevMap)]);
|
||||||
const expansion = {
|
const expansion = {
|
||||||
new_clients: { count: 0, revenue: 0 },
|
new_clients: { count: 0, revenue: 0 },
|
||||||
expansion: { count: 0, revenue: 0 },
|
expansion: { count: 0, revenue: 0 },
|
||||||
stable: { count: 0, revenue: 0 },
|
stable: { count: 0, revenue: 0 },
|
||||||
contraction: { count: 0, revenue: 0 },
|
contraction: { count: 0, revenue: 0 },
|
||||||
churned: { count: 0, revenue: 0 }
|
churned: { count: 0, revenue: 0 }
|
||||||
};
|
};
|
||||||
allClientIds.forEach(id => {
|
allClientIds.forEach(id => {
|
||||||
const curr = currMap[id];
|
const curr = currMap[id];
|
||||||
const prev = prevMap[id];
|
const prev = prevMap[id];
|
||||||
if (curr && !prev) {
|
if (curr && !prev) {
|
||||||
expansion.new_clients.count++; expansion.new_clients.revenue += curr.revenue;
|
expansion.new_clients.count++; expansion.new_clients.revenue += curr.revenue;
|
||||||
} else if (!curr && prev) {
|
} else if (!curr && prev) {
|
||||||
expansion.churned.count++; expansion.churned.revenue -= Math.abs(prev.revenue);
|
expansion.churned.count++; expansion.churned.revenue -= Math.abs(prev.revenue);
|
||||||
} else if (curr && prev) {
|
} else if (curr && prev) {
|
||||||
const absP = Math.abs(prev.revenue);
|
const absP = Math.abs(prev.revenue);
|
||||||
const change = absP > 0 ? (curr.revenue - prev.revenue) / absP : 0;
|
const change = absP > 0 ? (curr.revenue - prev.revenue) / absP : 0;
|
||||||
if (change > 0.1) {
|
if (change > 0.1) {
|
||||||
expansion.expansion.count++; expansion.expansion.revenue += (curr.revenue - prev.revenue);
|
expansion.expansion.count++; expansion.expansion.revenue += (curr.revenue - prev.revenue);
|
||||||
} else if (change < -0.1) {
|
} else if (change < -0.1) {
|
||||||
expansion.contraction.count++; expansion.contraction.revenue += (curr.revenue - prev.revenue);
|
expansion.contraction.count++; expansion.contraction.revenue += (curr.revenue - prev.revenue);
|
||||||
} else {
|
|
||||||
expansion.stable.count++; expansion.stable.revenue += curr.revenue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// === 3. CROSS-SELL ===
|
|
||||||
const [crossSellData] = await conn.execute(`
|
|
||||||
SELECT c.id_conta, t.vol_usd as pay_vol, p.vol_usd as checkout_vol
|
|
||||||
FROM (
|
|
||||||
SELECT DISTINCT id_conta FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
|
||||||
UNION
|
|
||||||
SELECT DISTINCT id_conta FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
|
||||||
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
|
||||||
) c
|
|
||||||
LEFT JOIN (
|
|
||||||
SELECT id_conta, ROUND(SUM(amount_usd), 2) as vol_usd
|
|
||||||
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
|
||||||
GROUP BY id_conta
|
|
||||||
) t ON t.id_conta = c.id_conta
|
|
||||||
LEFT JOIN (
|
|
||||||
SELECT id_conta, ROUND(SUM(valor), 2) as vol_usd
|
|
||||||
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
|
||||||
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
|
||||||
GROUP BY id_conta
|
|
||||||
) p ON p.id_conta = c.id_conta
|
|
||||||
`, [dataInicio, dataFim, dataInicio, dataFim, dataInicio, dataFim, dataInicio, dataFim]);
|
|
||||||
|
|
||||||
const crossSell = { pay_only: { count: 0, vol: 0 }, checkout_only: { count: 0, vol: 0 }, both: { count: 0, vol: 0 } };
|
|
||||||
crossSellData.forEach(r => {
|
|
||||||
const pv = Number(r.pay_vol) || 0;
|
|
||||||
const cv = Number(r.checkout_vol) || 0;
|
|
||||||
if (pv > 0 && cv > 0) { crossSell.both.count++; crossSell.both.vol += pv + cv; }
|
|
||||||
else if (pv > 0) { crossSell.pay_only.count++; crossSell.pay_only.vol += pv; }
|
|
||||||
else if (cv > 0) { crossSell.checkout_only.count++; crossSell.checkout_only.vol += cv; }
|
|
||||||
});
|
|
||||||
|
|
||||||
// === 4. CLIENT MATURITY SEGMENTS ===
|
|
||||||
const [maturityData] = await conn.execute(`
|
|
||||||
SELECT id_conta,
|
|
||||||
MIN(first_op) as first_op,
|
|
||||||
FLOOR(DATEDIFF(CURDATE(), MIN(first_op)) / 30.44) as months_active,
|
|
||||||
SUM(vol_usd) as lifetime_vol,
|
|
||||||
SUM(CASE WHEN period = 'curr' THEN vol_usd ELSE 0 END) as curr_vol,
|
|
||||||
SUM(CASE WHEN period = 'prev' THEN vol_usd ELSE 0 END) as prev_vol
|
|
||||||
FROM (
|
|
||||||
SELECT id_conta, MIN(created_at) as first_op, SUM(amount_usd) as vol_usd, 'all' as period
|
|
||||||
FROM br_transaction_to_usa GROUP BY id_conta
|
|
||||||
UNION ALL
|
|
||||||
SELECT id_conta, MIN(created_at) as first_op, SUM(valor) as vol_usd, 'all' as period
|
|
||||||
FROM pagamento_br WHERE cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
|
||||||
GROUP BY id_conta
|
|
||||||
UNION ALL
|
|
||||||
SELECT id_conta, NULL, SUM(amount_usd) as vol_usd, 'curr' as period
|
|
||||||
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ? GROUP BY id_conta
|
|
||||||
UNION ALL
|
|
||||||
SELECT id_conta, NULL, SUM(valor) as vol_usd, 'curr' as period
|
|
||||||
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
|
||||||
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
|
||||||
GROUP BY id_conta
|
|
||||||
UNION ALL
|
|
||||||
SELECT id_conta, NULL, SUM(amount_usd) as vol_usd, 'prev' as period
|
|
||||||
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ? GROUP BY id_conta
|
|
||||||
UNION ALL
|
|
||||||
SELECT id_conta, NULL, SUM(valor) as vol_usd, 'prev' as period
|
|
||||||
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
|
||||||
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
|
||||||
GROUP BY id_conta
|
|
||||||
) combined GROUP BY id_conta
|
|
||||||
`, [dataInicio, dataFim, dataInicio, dataFim, prevStartStr, prevEndStr, prevStartStr, prevEndStr]);
|
|
||||||
|
|
||||||
const maturity = { new_client: { count: 0, vol: 0 }, growing: { count: 0, vol: 0 }, mature: { count: 0, vol: 0 }, declining: { count: 0, vol: 0 } };
|
|
||||||
maturityData.forEach(r => {
|
|
||||||
const months = Number(r.months_active) || 0;
|
|
||||||
const cv = Number(r.curr_vol) || 0;
|
|
||||||
const pv = Number(r.prev_vol) || 0;
|
|
||||||
const lv = Number(r.lifetime_vol) || 0;
|
|
||||||
if (months < 3) {
|
|
||||||
maturity.new_client.count++; maturity.new_client.vol += lv;
|
|
||||||
} else if (pv > 0 && cv < pv * 0.85) {
|
|
||||||
maturity.declining.count++; maturity.declining.vol += lv;
|
|
||||||
} else if (months >= 12) {
|
|
||||||
maturity.mature.count++; maturity.mature.vol += lv;
|
|
||||||
} else {
|
} else {
|
||||||
maturity.growing.count++; maturity.growing.vol += lv;
|
expansion.stable.count++; expansion.stable.revenue += curr.revenue;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
return expansion;
|
||||||
|
}
|
||||||
|
|
||||||
return { cohorts, expansion, crossSell, maturity };
|
async function _fetchCrossSell(conn, dataInicio, dataFim) {
|
||||||
} finally {
|
const [crossSellData] = await conn.execute(`
|
||||||
conn.release();
|
SELECT c.id_conta, t.vol_usd as pay_vol, p.vol_usd as checkout_vol
|
||||||
}
|
FROM (
|
||||||
|
SELECT DISTINCT id_conta FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
|
UNION
|
||||||
|
SELECT DISTINCT id_conta FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
|
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
||||||
|
) c
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT id_conta, ROUND(SUM(amount_usd), 2) as vol_usd
|
||||||
|
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
|
GROUP BY id_conta
|
||||||
|
) t ON t.id_conta = c.id_conta
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT id_conta, ROUND(SUM(valor), 2) as vol_usd
|
||||||
|
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
|
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
||||||
|
GROUP BY id_conta
|
||||||
|
) p ON p.id_conta = c.id_conta
|
||||||
|
`, [dataInicio, dataFim, dataInicio, dataFim, dataInicio, dataFim, dataInicio, dataFim]);
|
||||||
|
|
||||||
|
const crossSell = { pay_only: { count: 0, vol: 0 }, checkout_only: { count: 0, vol: 0 }, both: { count: 0, vol: 0 } };
|
||||||
|
crossSellData.forEach(r => {
|
||||||
|
const pv = Number(r.pay_vol) || 0;
|
||||||
|
const cv = Number(r.checkout_vol) || 0;
|
||||||
|
if (pv > 0 && cv > 0) { crossSell.both.count++; crossSell.both.vol += pv + cv; }
|
||||||
|
else if (pv > 0) { crossSell.pay_only.count++; crossSell.pay_only.vol += pv; }
|
||||||
|
else if (cv > 0) { crossSell.checkout_only.count++; crossSell.checkout_only.vol += cv; }
|
||||||
|
});
|
||||||
|
return crossSell;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function _fetchMaturity(conn, dataInicio, dataFim, prevStartStr, prevEndStr) {
|
||||||
|
const [maturityData] = await conn.execute(`
|
||||||
|
SELECT id_conta,
|
||||||
|
MIN(first_op) as first_op,
|
||||||
|
FLOOR(DATEDIFF(CURDATE(), MIN(first_op)) / 30.44) as months_active,
|
||||||
|
SUM(vol_usd) as lifetime_vol,
|
||||||
|
SUM(CASE WHEN period = 'curr' THEN vol_usd ELSE 0 END) as curr_vol,
|
||||||
|
SUM(CASE WHEN period = 'prev' THEN vol_usd ELSE 0 END) as prev_vol
|
||||||
|
FROM (
|
||||||
|
SELECT id_conta, MIN(created_at) as first_op, SUM(amount_usd) as vol_usd, 'all' as period
|
||||||
|
FROM br_transaction_to_usa GROUP BY id_conta
|
||||||
|
UNION ALL
|
||||||
|
SELECT id_conta, MIN(created_at) as first_op, SUM(valor) as vol_usd, 'all' as period
|
||||||
|
FROM pagamento_br WHERE cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
||||||
|
GROUP BY id_conta
|
||||||
|
UNION ALL
|
||||||
|
SELECT id_conta, NULL, SUM(amount_usd) as vol_usd, 'curr' as period
|
||||||
|
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ? GROUP BY id_conta
|
||||||
|
UNION ALL
|
||||||
|
SELECT id_conta, NULL, SUM(valor) as vol_usd, 'curr' as period
|
||||||
|
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
|
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
||||||
|
GROUP BY id_conta
|
||||||
|
UNION ALL
|
||||||
|
SELECT id_conta, NULL, SUM(amount_usd) as vol_usd, 'prev' as period
|
||||||
|
FROM br_transaction_to_usa WHERE DATE(created_at) >= ? AND DATE(created_at) <= ? GROUP BY id_conta
|
||||||
|
UNION ALL
|
||||||
|
SELECT id_conta, NULL, SUM(valor) as vol_usd, 'prev' as period
|
||||||
|
FROM pagamento_br WHERE DATE(created_at) >= ? AND DATE(created_at) <= ?
|
||||||
|
AND cotacao IS NOT NULL AND cotacao > 0 AND (pgto IS NULL OR pgto != 'balance')
|
||||||
|
GROUP BY id_conta
|
||||||
|
) combined GROUP BY id_conta
|
||||||
|
`, [dataInicio, dataFim, dataInicio, dataFim, prevStartStr, prevEndStr, prevStartStr, prevEndStr]);
|
||||||
|
|
||||||
|
const maturity = { new_client: { count: 0, vol: 0 }, growing: { count: 0, vol: 0 }, mature: { count: 0, vol: 0 }, declining: { count: 0, vol: 0 } };
|
||||||
|
maturityData.forEach(r => {
|
||||||
|
const months = Number(r.months_active) || 0;
|
||||||
|
const cv = Number(r.curr_vol) || 0;
|
||||||
|
const pv = Number(r.prev_vol) || 0;
|
||||||
|
const lv = Number(r.lifetime_vol) || 0;
|
||||||
|
if (months < 3) {
|
||||||
|
maturity.new_client.count++; maturity.new_client.vol += lv;
|
||||||
|
} else if (pv > 0 && cv < pv * 0.85) {
|
||||||
|
maturity.declining.count++; maturity.declining.vol += lv;
|
||||||
|
} else if (months >= 12) {
|
||||||
|
maturity.mature.count++; maturity.mature.vol += lv;
|
||||||
|
} else {
|
||||||
|
maturity.growing.count++; maturity.growing.vol += lv;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return maturity;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
Reference in New Issue
Block a user