| 1 | import { prisma } from "@app/lib/prisma";
|
| 2 | import { InsightType, InsightPeriod } from "@prisma/client";
|
| 3 | import { InsightPeriodPayload } from "@app/lib/queue/types";
|
| 4 | import { logger } from "@app/lib/logger";
|
| 5 | // XXX: date-fns?
|
| 6 | import { formatHours, formatDiff } from "@app/utils/calc";
|
| 7 | import { startOfDay, addDays, subDays } from "date-fns";
|
| 8 |
|
| 9 | export async function processDailySleep({
|
| 10 | userId,
|
| 11 | periodStart,
|
| 12 | }: InsightPeriodPayload): Promise<void> {
|
| 13 | const today = startOfDay(new Date(periodStart));
|
| 14 | const tomorrow = addDays(today, 1);
|
| 15 | const yesterday = subDays(today, 1);
|
| 16 |
|
| 17 | const [todayRecords, yesterdayRecords] = await Promise.all([
|
| 18 | prisma.sleepRecord.findMany({
|
| 19 | where: { userId, date: { gte: today, lt: tomorrow } },
|
| 20 | select: { average: true },
|
| 21 | }),
|
| 22 | prisma.sleepRecord.findMany({
|
| 23 | where: { userId, date: { gte: yesterday, lt: today } },
|
| 24 | select: { average: true },
|
| 25 | }),
|
| 26 | ]);
|
| 27 |
|
| 28 | logger.debug(
|
| 29 | { todayRecords, yesterdayRecords },
|
| 30 | "[insights] daily sleep records",
|
| 31 | );
|
| 32 |
|
| 33 | if (todayRecords.length === 0) return;
|
| 34 |
|
| 35 | // average is stored in hours
|
| 36 | // sum across all records for the day
|
| 37 | const totalToday = todayRecords.reduce(
|
| 38 | (sum: number, r: { average: number }) => sum + r.average,
|
| 39 | 0,
|
| 40 | );
|
| 41 | const totalYesterday =
|
| 42 | yesterdayRecords.length > 0
|
| 43 | ? yesterdayRecords.reduce(
|
| 44 | (sum: number, r: { average: number }) => sum + r.average,
|
| 45 | 0,
|
| 46 | )
|
| 47 | : null;
|
| 48 |
|
| 49 | const diffMinutes =
|
| 50 | totalYesterday !== null
|
| 51 | ? Math.round((totalToday - totalYesterday) * 60)
|
| 52 | : null;
|
| 53 |
|
| 54 | const sessionCount = todayRecords.length; // 1 = normal night, 2+ = nap(s)
|
| 55 |
|
| 56 | const diffStr =
|
| 57 | diffMinutes !== null && Math.abs(diffMinutes) >= 5
|
| 58 | ? `${formatDiff(diffMinutes)} que ontem`
|
| 59 | : "";
|
| 60 |
|
| 61 | const napStr =
|
| 62 | sessionCount > 1
|
| 63 | ? `incluindo ${sessionCount - 1} cochilo${sessionCount > 2 ? "s" : ""}`
|
| 64 | : "";
|
| 65 |
|
| 66 | await prisma.insight.upsert({
|
| 67 | where: {
|
| 68 | userId_type_periodStart: {
|
| 69 | userId,
|
| 70 | type: InsightType.DAILY_SLEEP,
|
| 71 | periodStart: today,
|
| 72 | },
|
| 73 | },
|
| 74 | update: {
|
| 75 | body: buildBody(totalToday),
|
| 76 | metadata: {
|
| 77 | totalHours: totalToday,
|
| 78 | diffMinutes,
|
| 79 | diffStr,
|
| 80 | sessionCount,
|
| 81 | napStr,
|
| 82 | hadNaps: sessionCount > 1,
|
| 83 | },
|
| 84 | generatedAt: new Date(),
|
| 85 | },
|
| 86 | create: {
|
| 87 | userId,
|
| 88 | type: InsightType.DAILY_SLEEP,
|
| 89 | period: InsightPeriod.DAILY,
|
| 90 | title: "Sono",
|
| 91 | body: buildBody(totalToday),
|
| 92 | metadata: {
|
| 93 | totalHours: totalToday,
|
| 94 | diffMinutes,
|
| 95 | sessionCount,
|
| 96 | diffStr,
|
| 97 | napStr,
|
| 98 | hadNaps: sessionCount > 1,
|
| 99 | },
|
| 100 | periodStart: today,
|
| 101 | periodEnd: tomorrow,
|
| 102 | },
|
| 103 | });
|
| 104 | }
|
| 105 |
|
| 106 | function buildBody(total: number): string {
|
| 107 | const totalStr = formatHours(total);
|
| 108 |
|
| 109 | return `${totalStr} de sono hoje`;
|
| 110 | }
|