| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- <?php
- namespace Models;
- use Libs\Database;
- class EvolutionOverviewModel
- {
- private \PDO $pdo;
- public function __construct()
- {
- $this->pdo = Database::pdo();
- }
- public function getOverviewData(int $companyId, array $queryParams = []): array
- {
- $anchorDate = $this->getAnchorDate($companyId);
- $period = $this->normalizePeriod($queryParams);
- $range = $this->resolveRange($anchorDate, $period);
- $sentimentSeries = $this->getSentimentSeries($companyId, $range);
- $playbooksSeries = $this->getPlaybooksSeries($companyId, $range);
- $playbooksTotals = $this->buildPlaybooksTotals($playbooksSeries);
- $kpiSnapshots = $this->getRecentKpiSnapshots($companyId, $anchorDate);
- $recentSentimentScores = $this->getRecentSentimentScores($companyId, $anchorDate);
- return [
- 'kpis' => $this->buildKpis($kpiSnapshots, $recentSentimentScores, $playbooksTotals),
- 'sentimentSeries' => $sentimentSeries,
- 'playbooksSeries' => $playbooksSeries,
- 'playbooksTotals' => $playbooksTotals,
- ];
- }
- private function getAnchorDate(int $companyId): \DateTimeImmutable
- {
- $stmt = $this->pdo->prepare(
- "SELECT MAX(snapshot_date)
- FROM (
- SELECT MAX(evolution_snapshot_date)::date AS snapshot_date
- FROM sentiment_evolution
- WHERE company_id = :company_id
- UNION ALL
- SELECT MAX(playbook_snapshot_date)::date AS snapshot_date
- FROM playbooks_monitor
- WHERE company_id = :company_id
- UNION ALL
- SELECT MAX(kpi_snapshot_date)::date AS snapshot_date
- FROM kpi_snapshot
- WHERE company_id = :company_id
- ) snapshots"
- );
- $stmt->execute(['company_id' => $companyId]);
- $anchorDate = $stmt->fetchColumn();
- if (!is_string($anchorDate) || $anchorDate === '') {
- return new \DateTimeImmutable('today');
- }
- return new \DateTimeImmutable($anchorDate);
- }
- private function normalizePeriod(array $queryParams): string
- {
- $period = strtolower(trim((string) ($queryParams['period'] ?? 'week')));
- if (!in_array($period, ['week', 'month', 'quarter'], true)) {
- $period = 'week';
- }
- return $period;
- }
- private function resolveRange(\DateTimeImmutable $anchorDate, string $period): array
- {
- if ($period === 'quarter') {
- $startDate = $anchorDate->modify('-89 days');
- } elseif ($period === 'month') {
- $startDate = $anchorDate->modify('-29 days');
- } else {
- $startDate = $anchorDate->modify('-6 days');
- }
- return [
- 'start_date' => $startDate->format('Y-m-d'),
- 'end_date' => $anchorDate->format('Y-m-d'),
- ];
- }
- private function getSentimentSeries(int $companyId, array $range): array
- {
- $stmt = $this->pdo->prepare(
- "SELECT
- evolution_snapshot_date,
- evolution_sentiment_score
- FROM sentiment_evolution
- WHERE company_id = :company_id
- AND evolution_snapshot_date >= :start_date
- AND evolution_snapshot_date <= :end_date
- ORDER BY evolution_snapshot_date ASC"
- );
- $stmt->execute([
- 'company_id' => $companyId,
- 'start_date' => $range['start_date'],
- 'end_date' => $range['end_date'],
- ]);
- $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
- $mapped = [];
- foreach ($rows as $row) {
- $mapped[$row['evolution_snapshot_date']] = (float) ($row['evolution_sentiment_score'] ?? 0);
- }
- $items = [];
- $current = new \DateTimeImmutable($range['start_date']);
- $end = new \DateTimeImmutable($range['end_date']);
- while ($current <= $end) {
- $date = $current->format('Y-m-d');
- $items[] = [
- 'date' => $date,
- 'value' => (float) ($mapped[$date] ?? 0),
- ];
- $current = $current->modify('+1 day');
- }
- return $items;
- }
- private function getPlaybooksSeries(int $companyId, array $range): array
- {
- $stmt = $this->pdo->prepare(
- "SELECT
- playbook_snapshot_date,
- playbook_new_detected,
- playbook_converted
- FROM playbooks_monitor
- WHERE company_id = :company_id
- AND playbook_snapshot_date >= :start_date
- AND playbook_snapshot_date <= :end_date
- ORDER BY playbook_snapshot_date ASC"
- );
- $stmt->execute([
- 'company_id' => $companyId,
- 'start_date' => $range['start_date'],
- 'end_date' => $range['end_date'],
- ]);
- $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
- $mapped = [];
- foreach ($rows as $row) {
- $mapped[$row['playbook_snapshot_date']] = [
- 'novos' => (int) ($row['playbook_new_detected'] ?? 0),
- 'convertidos' => (int) ($row['playbook_converted'] ?? 0),
- ];
- }
- $items = [];
- $current = new \DateTimeImmutable($range['start_date']);
- $end = new \DateTimeImmutable($range['end_date']);
- while ($current <= $end) {
- $date = $current->format('Y-m-d');
- $items[] = [
- 'date' => $date,
- 'novos' => (int) (($mapped[$date]['novos'] ?? 0)),
- 'convertidos' => (int) (($mapped[$date]['convertidos'] ?? 0)),
- ];
- $current = $current->modify('+1 day');
- }
- return $items;
- }
- private function buildPlaybooksTotals(array $playbooksSeries): array
- {
- $novos = 0;
- $convertidos = 0;
- foreach ($playbooksSeries as $item) {
- $novos += (int) ($item['novos'] ?? 0);
- $convertidos += (int) ($item['convertidos'] ?? 0);
- }
- return [
- 'novos' => $novos,
- 'convertidos' => $convertidos,
- ];
- }
- private function getRecentKpiSnapshots(int $companyId, \DateTimeImmutable $anchorDate): array
- {
- $stmt = $this->pdo->prepare(
- "SELECT
- kpi_snapshot_date,
- kpi_current_sales,
- kpi_avg_ticket,
- kpi_lifetime_at_risk
- FROM kpi_snapshot
- WHERE company_id = :company_id
- AND kpi_snapshot_date <= :anchor_date
- ORDER BY kpi_snapshot_date DESC
- LIMIT 2"
- );
- $stmt->execute([
- 'company_id' => $companyId,
- 'anchor_date' => $anchorDate->format('Y-m-d'),
- ]);
- return $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
- }
- private function getRecentSentimentScores(int $companyId, \DateTimeImmutable $anchorDate): array
- {
- $stmt = $this->pdo->prepare(
- "SELECT
- evolution_snapshot_date,
- evolution_sentiment_score
- FROM sentiment_evolution
- WHERE company_id = :company_id
- AND evolution_snapshot_date <= :anchor_date
- ORDER BY evolution_snapshot_date DESC
- LIMIT 2"
- );
- $stmt->execute([
- 'company_id' => $companyId,
- 'anchor_date' => $anchorDate->format('Y-m-d'),
- ]);
- return $stmt->fetchAll(\PDO::FETCH_ASSOC) ?: [];
- }
- private function buildKpis(array $kpiSnapshots, array $recentSentimentScores, array $playbooksTotals): array
- {
- $latestKpi = $kpiSnapshots[0] ?? [];
- $previousKpi = $kpiSnapshots[1] ?? $latestKpi;
- $latestAtRisk = (float) ($latestKpi['kpi_lifetime_at_risk'] ?? 0);
- $previousAtRisk = (float) ($previousKpi['kpi_lifetime_at_risk'] ?? $latestAtRisk);
- $currentSales = (float) ($latestKpi['kpi_current_sales'] ?? 0);
- $latestSentiment = isset($recentSentimentScores[0]['evolution_sentiment_score'])
- ? (float) $recentSentimentScores[0]['evolution_sentiment_score']
- : 0.0;
- $previousSentiment = isset($recentSentimentScores[1]['evolution_sentiment_score'])
- ? (float) $recentSentimentScores[1]['evolution_sentiment_score']
- : $latestSentiment;
- $latestScore = $this->normalizeSentimentScore($latestSentiment);
- $previousScore = $this->normalizeSentimentScore($previousSentiment);
- if ($previousScore === 0.0) {
- $evolutionRate = $latestScore > 0 ? 100.0 : 0.0;
- } else {
- $evolutionRate = (($latestScore - $previousScore) / abs($previousScore)) * 100;
- }
- $novos = (int) ($playbooksTotals['novos'] ?? 0);
- $convertidos = (int) ($playbooksTotals['convertidos'] ?? 0);
- return [
- 'churnEvitado' => round(max(0, $previousAtRisk - $latestAtRisk), 2),
- 'roiUpsell' => round($latestAtRisk > 0 ? ($currentSales / $latestAtRisk) : 0, 1),
- 'scoreMedio' => (int) round($latestScore),
- 'taxaEvolucao' => round($evolutionRate, 1),
- 'conversaoEmocao' => $novos > 0 ? (int) round(($convertidos / $novos) * 100) : 0,
- ];
- }
- private function normalizeSentimentScore(float $score): float
- {
- $normalized = (($score + 1) / 2) * 100;
- return max(0, min(100, $normalized));
- }
- }
|