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