eletrotupi / tcc/ api/src/lib/queue/processors/insights/dailyEnergy.tsde0605fcb0d1980cedfafe4830a7aeaef31de5dc
3.4 KB | 121 lines Raw
1
import { prisma } from "@app/lib/prisma";
2
import { InsightType, InsightPeriod } from "@prisma/client";
3
import { InsightPeriodPayload } from "@app/lib/queue/types";
4
import { avg } from "@app/utils/calc";
5
import {
6
  appDayWindow,
7
  previousAppDay,
8
  DAY_WINDOW_LABEL,
9
} from "@app/lib/queue/processors/insights/utils";
10

11
export async function processDailyEnergy({
12
  userId,
13
  periodStart,
14
}: InsightPeriodPayload): Promise<void> {
15
  const { start, end } = appDayWindow(new Date(periodStart));
16
  const yesterdayStart = previousAppDay(start);
17

18
  const [moods, yesterdayMoods] = await Promise.all([
19
    prisma.mood.findMany({
20
      where: { userId, moment: { gte: start, lt: end } },
21
      orderBy: { moment: "asc" },
22
      select: {
23
        energyLevel: true,
24
        stressLevel: true,
25
        anxietyLevel: true,
26
        selectedMood: true,
27
        moment: true,
28
      },
29
    }),
30
    prisma.mood.findMany({
31
      where: { userId, moment: { gte: yesterdayStart, lt: start } },
32
      select: { energyLevel: true },
33
    }),
34
  ]);
35

36
  if (moods.length === 0) return;
37

38
  const avgEnergy = parseFloat(avg(moods.map((m) => m.energyLevel)).toFixed(2));
39
  const avgStress = parseFloat(avg(moods.map((m) => m.stressLevel)).toFixed(2));
40
  const avgAnxiety = parseFloat(
41
    avg(moods.map((m) => m.anxietyLevel)).toFixed(2),
42
  );
43
  const peak = Math.max(...moods.map((m) => m.energyLevel));
44
  const trough = Math.min(...moods.map((m) => m.energyLevel));
45

46
  // Trend across the day: compare first half vs second half entries
47
  // Not that great, but it works
48
  const mid = Math.floor(moods.length / 2);
49
  const morningAvg =
50
    moods.length > 1
51
      ? avg(moods.slice(0, mid).map((m) => m.energyLevel))
52
      : null;
53

54
  const eveningAvg =
55
    moods.length > 1 ? avg(moods.slice(mid).map((m) => m.energyLevel)) : null;
56

57
  const dayTrend =
58
    morningAvg && eveningAvg
59
      ? parseFloat((((eveningAvg - morningAvg) / morningAvg) * 100).toFixed(1))
60
      : null;
61

62
  const yesterdayAvg =
63
    yesterdayMoods.length > 0
64
      ? parseFloat(avg(yesterdayMoods.map((m) => m.energyLevel)).toFixed(2))
65
      : null;
66

67
  // Positive means more energy than yesterday; null when there is nothing to
68
  // compare against
69
  const diffEnergy =
70
    yesterdayAvg !== null
71
      ? parseFloat((avgEnergy - yesterdayAvg).toFixed(2))
72
      : null;
73

74
  const metadata = {
75
    avgEnergy,
76
    avgStress,
77
    avgAnxiety,
78
    peak,
79
    trough,
80
    dayTrend,
81
    entryCount: moods.length,
82
    yesterdayAvg,
83
    diffEnergy,
84
    windowLabel: DAY_WINDOW_LABEL,
85
  };
86

87
  await prisma.insight.upsert({
88
    where: {
89
      userId_type_periodStart: {
90
        userId,
91
        type: InsightType.DAILY_ENERGY,
92
        periodStart: start,
93
      },
94
    },
95
    update: {
96
      body: buildBody(avgEnergy, dayTrend),
97
      metadata,
98
      generatedAt: new Date(),
99
    },
100
    create: {
101
      userId,
102
      type: InsightType.DAILY_ENERGY,
103
      period: InsightPeriod.DAILY,
104
      title: "Energia do Dia",
105
      body: buildBody(avgEnergy, dayTrend),
106
      metadata,
107
      periodStart: start,
108
      periodEnd: end,
109
    },
110
  });
111
}
112

113
function buildBody(avgEnergy: number, dayTrend: number | null): string {
114
  const level = avgEnergy >= 7 ? "alta" : avgEnergy >= 4 ? "moderada" : "baixa";
115
  if (dayTrend === null) return `Energia ${level} hoje.`;
116
  if (dayTrend > 10)
117
    return `Energia ${level} hoje, com melhora ao longo do dia.`;
118
  if (dayTrend < -10)
119
    return `Energia ${level} hoje, com queda ao longo do dia.`;
120
  return `Energia ${level} e estável ao longo do dia.`;
121
}