| 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 | }
|