| 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 { appDayWindow } from "@app/lib/queue/processors/insights/utils";
|
| 6 |
|
| 7 | export async function processDailyEnergy({
|
| 8 | userId,
|
| 9 | periodStart,
|
| 10 | }: InsightPeriodPayload): Promise<void> {
|
| 11 | const { start, end } = appDayWindow(new Date(periodStart));
|
| 12 |
|
| 13 | const moods = await prisma.mood.findMany({
|
| 14 | where: { userId, moment: { gte: start, lt: end } },
|
| 15 | orderBy: { moment: "asc" },
|
| 16 | select: {
|
| 17 | energyLevel: true,
|
| 18 | stressLevel: true,
|
| 19 | anxietyLevel: true,
|
| 20 | selectedMood: true,
|
| 21 | moment: true,
|
| 22 | },
|
| 23 | });
|
| 24 |
|
| 25 | if (moods.length === 0) return;
|
| 26 |
|
| 27 | const avgEnergy = parseFloat(avg(moods.map((m) => m.energyLevel)).toFixed(2));
|
| 28 | const avgStress = parseFloat(avg(moods.map((m) => m.stressLevel)).toFixed(2));
|
| 29 | const avgAnxiety = parseFloat(
|
| 30 | avg(moods.map((m) => m.anxietyLevel)).toFixed(2),
|
| 31 | );
|
| 32 | const peak = Math.max(...moods.map((m) => m.energyLevel));
|
| 33 | const trough = Math.min(...moods.map((m) => m.energyLevel));
|
| 34 |
|
| 35 | // Trend across the day: compare first half vs second half entries
|
| 36 | // Not that great, but it works
|
| 37 | const mid = Math.floor(moods.length / 2);
|
| 38 | const morningAvg =
|
| 39 | moods.length > 1
|
| 40 | ? avg(moods.slice(0, mid).map((m) => m.energyLevel))
|
| 41 | : null;
|
| 42 |
|
| 43 | const eveningAvg =
|
| 44 | moods.length > 1 ? avg(moods.slice(mid).map((m) => m.energyLevel)) : null;
|
| 45 |
|
| 46 | const dayTrend =
|
| 47 | morningAvg && eveningAvg
|
| 48 | ? parseFloat((((eveningAvg - morningAvg) / morningAvg) * 100).toFixed(1))
|
| 49 | : null;
|
| 50 |
|
| 51 | await prisma.insight.upsert({
|
| 52 | where: {
|
| 53 | userId_type_periodStart: {
|
| 54 | userId,
|
| 55 | type: InsightType.DAILY_ENERGY,
|
| 56 | periodStart: start,
|
| 57 | },
|
| 58 | },
|
| 59 | update: {
|
| 60 | body: buildBody(avgEnergy, dayTrend),
|
| 61 | metadata: {
|
| 62 | avgEnergy,
|
| 63 | avgStress,
|
| 64 | avgAnxiety,
|
| 65 | peak,
|
| 66 | trough,
|
| 67 | dayTrend,
|
| 68 | entryCount: moods.length,
|
| 69 | },
|
| 70 | generatedAt: new Date(),
|
| 71 | },
|
| 72 | create: {
|
| 73 | userId,
|
| 74 | type: InsightType.DAILY_ENERGY,
|
| 75 | period: InsightPeriod.DAILY,
|
| 76 | title: "Energia do Dia",
|
| 77 | body: buildBody(avgEnergy, dayTrend),
|
| 78 | metadata: {
|
| 79 | avgEnergy,
|
| 80 | avgStress,
|
| 81 | avgAnxiety,
|
| 82 | peak,
|
| 83 | trough,
|
| 84 | dayTrend,
|
| 85 | entryCount: moods.length,
|
| 86 | },
|
| 87 | periodStart: start,
|
| 88 | periodEnd: end,
|
| 89 | },
|
| 90 | });
|
| 91 | }
|
| 92 |
|
| 93 | function buildBody(avgEnergy: number, dayTrend: number | null): string {
|
| 94 | const level = avgEnergy >= 7 ? "alta" : avgEnergy >= 4 ? "moderada" : "baixa";
|
| 95 | if (dayTrend === null) return `Energia ${level} hoje.`;
|
| 96 | if (dayTrend > 10)
|
| 97 | return `Energia ${level} hoje, com melhora ao longo do dia.`;
|
| 98 | if (dayTrend < -10)
|
| 99 | return `Energia ${level} hoje, com queda ao longo do dia.`;
|
| 100 | return `Energia ${level} e estável ao longo do dia.`;
|
| 101 | }
|