eletrotupi / tcc/ api/src/lib/queue/processors/insights/dailyEnergy.ts68b32b6167f98c0777e1cc816c4065363f24fba4
4.1 KB | 152 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
type EnergyEntry = {
12
  energyLevel: number;
13
  stressLevel: number;
14
  anxietyLevel: number;
15
};
16

17
export type DailyEnergyMetadata = {
18
  avgEnergy: number;
19
  avgStress: number;
20
  avgAnxiety: number;
21
  peak: number;
22
  trough: number;
23
  dayTrend: number | null;
24
  entryCount: number;
25
  yesterdayAvg: number | null;
26
  diffEnergy: number | null;
27
  windowLabel: string;
28
};
29

30
export function computeDailyEnergy(
31
  moods: EnergyEntry[],
32
  yesterdayMoods: { energyLevel: number }[],
33
): { metadata: DailyEnergyMetadata; dayTrend: number | null } {
34
  const avgEnergy = parseFloat(avg(moods.map((m) => m.energyLevel)).toFixed(2));
35
  const avgStress = parseFloat(avg(moods.map((m) => m.stressLevel)).toFixed(2));
36
  const avgAnxiety = parseFloat(
37
    avg(moods.map((m) => m.anxietyLevel)).toFixed(2),
38
  );
39
  const peak = Math.max(...moods.map((m) => m.energyLevel));
40
  const trough = Math.min(...moods.map((m) => m.energyLevel));
41

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

50
  const eveningAvg =
51
    moods.length > 1 ? avg(moods.slice(mid).map((m) => m.energyLevel)) : null;
52

53
  const dayTrend =
54
    morningAvg && eveningAvg
55
      ? parseFloat((((eveningAvg - morningAvg) / morningAvg) * 100).toFixed(1))
56
      : null;
57

58
  const yesterdayAvg =
59
    yesterdayMoods.length > 0
60
      ? parseFloat(avg(yesterdayMoods.map((m) => m.energyLevel)).toFixed(2))
61
      : null;
62

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

70
  const metadata: DailyEnergyMetadata = {
71
    avgEnergy,
72
    avgStress,
73
    avgAnxiety,
74
    peak,
75
    trough,
76
    dayTrend,
77
    entryCount: moods.length,
78
    yesterdayAvg,
79
    diffEnergy,
80
    windowLabel: DAY_WINDOW_LABEL,
81
  };
82

83
  return { metadata, dayTrend };
84
}
85

86
export function dailyEnergyBody(
87
  avgEnergy: number,
88
  dayTrend: number | null,
89
): string {
90
  const level = avgEnergy >= 7 ? "alta" : avgEnergy >= 4 ? "moderada" : "baixa";
91
  if (dayTrend === null) return `Energia ${level} hoje.`;
92
  if (dayTrend > 10)
93
    return `Energia ${level} hoje, com melhora ao longo do dia.`;
94
  if (dayTrend < -10)
95
    return `Energia ${level} hoje, com queda ao longo do dia.`;
96
  return `Energia ${level} e estável ao longo do dia.`;
97
}
98

99
export async function processDailyEnergy({
100
  userId,
101
  periodStart,
102
}: InsightPeriodPayload): Promise<void> {
103
  const { start, end } = appDayWindow(new Date(periodStart));
104
  const yesterdayStart = previousAppDay(start);
105

106
  const [moods, yesterdayMoods] = await Promise.all([
107
    prisma.mood.findMany({
108
      where: { userId, moment: { gte: start, lt: end } },
109
      orderBy: { moment: "asc" },
110
      select: {
111
        energyLevel: true,
112
        stressLevel: true,
113
        anxietyLevel: true,
114
        selectedMood: true,
115
        moment: true,
116
      },
117
    }),
118
    prisma.mood.findMany({
119
      where: { userId, moment: { gte: yesterdayStart, lt: start } },
120
      select: { energyLevel: true },
121
    }),
122
  ]);
123

124
  if (moods.length === 0) return;
125

126
  const { metadata, dayTrend } = computeDailyEnergy(moods, yesterdayMoods);
127

128
  await prisma.insight.upsert({
129
    where: {
130
      userId_type_periodStart: {
131
        userId,
132
        type: InsightType.DAILY_ENERGY,
133
        periodStart: start,
134
      },
135
    },
136
    update: {
137
      body: dailyEnergyBody(metadata.avgEnergy, dayTrend),
138
      metadata,
139
      generatedAt: new Date(),
140
    },
141
    create: {
142
      userId,
143
      type: InsightType.DAILY_ENERGY,
144
      period: InsightPeriod.DAILY,
145
      title: "Energia do Dia",
146
      body: dailyEnergyBody(metadata.avgEnergy, dayTrend),
147
      metadata,
148
      periodStart: start,
149
      periodEnd: end,
150
    },
151
  });
152
}