eletrotupi / tcc/ api/src/reports/report.data.tsc8bab449d13fa00dadc0138ef8f83f18104eb593
3.5 KB | 142 lines Raw
1
import { eachDayOfInterval, format } from 'date-fns'
2
import { prisma } from '@app/lib/prisma'
3
import { logger } from '@app/lib/logger'
4
import {
5
  BaseMoodOption,
6
  MoodComponentOption,
7
  IntensityLevel,
8
} from '@prisma/client'
9

10
export type ReportData = {
11
  user: { firstName: string; lastName: string | null }
12
  periodStart: Date
13
  periodEnd: Date
14
  totalCheckins: number
15
  moodDistribution: Array<{
16
    mood: BaseMoodOption
17
    count: number
18
    pct: number
19
  }>
20
  dailyMoods: Array<{ date: string; mood: BaseMoodOption | null }>
21
  topComponents: Array<{
22
    component: MoodComponentOption
23
    count: number
24
    avgIntensity: IntensityLevel
25
  }>
26
  checkinLog: Array<{
27
    moment: Date
28
    mood: BaseMoodOption
29
    anxietyLevel: number
30
    stressLevel: number
31
    energyLevel: number
32
    annotation: string | null
33
  }>
34
}
35

36
const MOOD_ORDER: BaseMoodOption[] = [
37
  'GREAT', 'GOOD', 'NEUTRAL', 'SAD', 'ANGRY',
38
]
39

40

41
export async function buildReportData(
42
  userId: number,
43
  start: Date,
44
  end: Date,
45
): Promise<ReportData> {
46
  const [user, moods] = await Promise.all([
47
    prisma.user.findUniqueOrThrow({
48
      where: { id: userId },
49
      select: { firstName: true, lastName: true },
50
    }),
51
    prisma.mood.findMany({
52
      where: {
53
        userId,
54
        moment: { gte: start, lte: end },
55
      },
56
      include: { moodComponents: true },
57
      orderBy: { moment: 'desc' },
58
    }),
59
  ])
60

61
  const total = moods.length
62

63
  logger.info(
64
    { count: moods.length, start, end },
65
    '[report] found mood entries',
66
  )
67

68
  const moodCounts = Object.fromEntries(
69
    MOOD_ORDER.map(m => [m, 0]),
70
  ) as Record<BaseMoodOption, number>
71

72
  for (const m of moods) moodCounts[m.selectedMood]++
73

74
  const moodDistribution = MOOD_ORDER.map(mood => ({
75
    mood,
76
    count: moodCounts[mood],
77
    pct: total === 0 ? 0 : Math.round((moodCounts[mood] / total) * 1000) / 10,
78
  }))
79

80
  const byDate = new Map<string, typeof moods[number][]>()
81
  for (const m of moods) {
82
    const key = format(m.moment, 'yyyy-MM-dd')
83
    const group = byDate.get(key) ?? []
84
    group.push(m)
85
    byDate.set(key, group)
86
  }
87

88
  const days = eachDayOfInterval({ start, end })
89
  const dailyMoods = days.map(day => {
90
    const key = format(day, 'yyyy-MM-dd')
91
    const group = byDate.get(key)
92
    if (!group || group.length === 0) return { date: key, mood: null }
93
    const last = group.reduce((a, b) =>
94
      b.moment > a.moment ? b : a,
95
    )
96
    return { date: key, mood: last.selectedMood }
97
  })
98

99
  const componentGroups = new Map<
100
    MoodComponentOption,
101
    IntensityLevel[]
102
  >()
103
  for (const m of moods) {
104
    for (const c of m.moodComponents) {
105
      const arr = componentGroups.get(c.component) ?? []
106
      arr.push(c.intensity)
107
      componentGroups.set(c.component, arr)
108
    }
109
  }
110

111
  const topComponents = Array.from(componentGroups.entries())
112
    .map(([component, intensities]) => {
113
      const freq = new Map<IntensityLevel, number>()
114
      for (const i of intensities) freq.set(i, (freq.get(i) ?? 0) + 1)
115
      const avgIntensity = Array.from(freq.entries()).reduce((a, b) =>
116
        b[1] > a[1] ? b : a,
117
      )[0]
118
      return { component, count: intensities.length, avgIntensity }
119
    })
120
    .sort((a, b) => b.count - a.count)
121
    .slice(0, 10)
122

123
  const checkinLog = moods.slice(0, 50).map(m => ({
124
    moment: m.moment,
125
    mood: m.selectedMood,
126
    anxietyLevel: m.anxietyLevel,
127
    stressLevel: m.stressLevel,
128
    energyLevel: m.energyLevel,
129
    annotation: m.annotation,
130
  }))
131

132
  return {
133
    user,
134
    periodStart: start,
135
    periodEnd: end,
136
    totalCheckins: total,
137
    moodDistribution,
138
    dailyMoods,
139
    topComponents,
140
    checkinLog,
141
  }
142
}