eletrotupi / tcc/ api/src/reports/report.data.tsf8e7845db0b429e46eeca7baf6e29fe1444d0492
3.5 KB | 138 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
  console.log(`[report] found ${moods.length} mood entries from ${start} - ${end}`)
63

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

68
  for (const m of moods) moodCounts[m.selectedMood]++
69

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

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

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

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

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

119
  const checkinLog = moods.slice(0, 50).map(m => ({
120
    moment: m.moment,
121
    mood: m.selectedMood,
122
    anxietyLevel: m.anxietyLevel,
123
    stressLevel: m.stressLevel,
124
    energyLevel: m.energyLevel,
125
    annotation: m.annotation,
126
  }))
127

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