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

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

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

39

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

60
  const total = moods.length
61

62
  const moodCounts = Object.fromEntries(
63
    MOOD_ORDER.map(m => [m, 0]),
64
  ) as Record<BaseMoodOption, number>
65

66
  for (const m of moods) moodCounts[m.selectedMood]++
67

68
  const moodDistribution = MOOD_ORDER.map(mood => ({
69
    mood,
70
    count: moodCounts[mood],
71
    pct: total === 0 ? 0 : Math.round((moodCounts[mood] / total) * 1000) / 10,
72
  }))
73

74
  const byDate = new Map<string, typeof moods[number][]>()
75
  for (const m of moods) {
76
    const key = format(m.moment, 'yyyy-MM-dd')
77
    const group = byDate.get(key) ?? []
78
    group.push(m)
79
    byDate.set(key, group)
80
  }
81

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

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
  const topComponents = 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
  const checkinLog = moods.slice(0, 50).map(m => ({
118
    moment: m.moment,
119
    mood: m.selectedMood,
120
    anxietyLevel: m.anxietyLevel,
121
    stressLevel: m.stressLevel,
122
    energyLevel: m.energyLevel,
123
    annotation: m.annotation,
124
  }))
125

126
  return {
127
    user,
128
    periodStart: start,
129
    periodEnd: end,
130
    totalCheckins: total,
131
    moodDistribution,
132
    dailyMoods,
133
    topComponents,
134
    checkinLog,
135
  }
136
}