eletrotupi / tcc/ api/src/reports/report.data.ts074b881232764e3f20a6308cce0c2027907a0fda
4.4 KB | 177 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 function buildMoodDistribution(
42
  moods: { selectedMood: BaseMoodOption }[],
43
  total: number,
44
): ReportData['moodDistribution'] {
45
  const moodCounts = Object.fromEntries(
46
    MOOD_ORDER.map(m => [m, 0]),
47
  ) as Record<BaseMoodOption, number>
48

49
  for (const m of moods) moodCounts[m.selectedMood]++
50

51
  return MOOD_ORDER.map(mood => ({
52
    mood,
53
    count: moodCounts[mood],
54
    pct: total === 0 ? 0 : Math.round((moodCounts[mood] / total) * 1000) / 10,
55
  }))
56
}
57

58
// One entry per day in the interval; the latest Mood Entry of the day wins
59
export function buildDailyMoods(
60
  moods: { moment: Date; selectedMood: BaseMoodOption }[],
61
  days: Date[],
62
): ReportData['dailyMoods'] {
63
  const byDate = new Map<
64
    string,
65
    { moment: Date; selectedMood: BaseMoodOption }[]
66
  >()
67
  for (const m of moods) {
68
    const key = format(m.moment, 'yyyy-MM-dd')
69
    const group = byDate.get(key) ?? []
70
    group.push(m)
71
    byDate.set(key, group)
72
  }
73

74
  return days.map(day => {
75
    const key = format(day, 'yyyy-MM-dd')
76
    const group = byDate.get(key)
77
    if (!group || group.length === 0) return { date: key, mood: null }
78
    const last = group.reduce((a, b) =>
79
      b.moment > a.moment ? b : a,
80
    )
81
    return { date: key, mood: last.selectedMood }
82
  })
83
}
84

85
export function buildTopComponents(
86
  moods: {
87
    moodComponents: {
88
      component: MoodComponentOption
89
      intensity: IntensityLevel
90
    }[]
91
  }[],
92
): ReportData['topComponents'] {
93
  const componentGroups = new Map<
94
    MoodComponentOption,
95
    IntensityLevel[]
96
  >()
97
  for (const m of moods) {
98
    for (const c of m.moodComponents) {
99
      const arr = componentGroups.get(c.component) ?? []
100
      arr.push(c.intensity)
101
      componentGroups.set(c.component, arr)
102
    }
103
  }
104

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

118
export function buildCheckinLog(
119
  moods: {
120
    moment: Date
121
    selectedMood: BaseMoodOption
122
    anxietyLevel: number
123
    stressLevel: number
124
    energyLevel: number
125
    annotation: string | null
126
  }[],
127
): ReportData['checkinLog'] {
128
  return moods.slice(0, 50).map(m => ({
129
    moment: m.moment,
130
    mood: m.selectedMood,
131
    anxietyLevel: m.anxietyLevel,
132
    stressLevel: m.stressLevel,
133
    energyLevel: m.energyLevel,
134
    annotation: m.annotation,
135
  }))
136
}
137

138
export async function buildReportData(
139
  userId: number,
140
  start: Date,
141
  end: Date,
142
): Promise<ReportData> {
143
  const [user, moods] = await Promise.all([
144
    prisma.user.findUniqueOrThrow({
145
      where: { id: userId },
146
      select: { firstName: true, lastName: true },
147
    }),
148
    prisma.mood.findMany({
149
      where: {
150
        userId,
151
        moment: { gte: start, lte: end },
152
      },
153
      include: { moodComponents: true },
154
      orderBy: { moment: 'desc' },
155
    }),
156
  ])
157

158
  const total = moods.length
159

160
  logger.info(
161
    { count: moods.length, start, end },
162
    '[report] found mood entries',
163
  )
164

165
  const days = eachDayOfInterval({ start, end })
166

167
  return {
168
    user,
169
    periodStart: start,
170
    periodEnd: end,
171
    totalCheckins: total,
172
    moodDistribution: buildMoodDistribution(moods, total),
173
    dailyMoods: buildDailyMoods(moods, days),
174
    topComponents: buildTopComponents(moods),
175
    checkinLog: buildCheckinLog(moods),
176
  }
177
}