Skip to content

Commit

Permalink
chore: compute aggregated results for all questions from detail respo…
Browse files Browse the repository at this point in the history
…nses (consistency) (#4100)

Co-authored-by: Roland Schläfli <rolandschlaefli@gmail.com>
  • Loading branch information
sjschlapbach and rschlaefli authored May 6, 2024
1 parent 76584a3 commit a03b927
Show file tree
Hide file tree
Showing 7 changed files with 476 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"ts-node": "10.9.1",
"ts-node-dev": "2.0.0",
"tsup": "7.2.0",
"tsx": "4.9.1",
"typescript": "5.1.6",
"web-push": "3.6.1"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ async function run() {

// check if the results follow the old logic with value: count instead of hash: {value, count, correct}
const isOldLogic =
Object.keys(responses).length === 0 ||
Object.values(responses).every((result) => typeof result === 'number')
Object.keys(responses).length > 0 &&
Object.values(responses).some((result) => typeof result === 'number')

if (isOldLogic) {
totalUpdates++
Expand Down
210 changes: 210 additions & 0 deletions packages/graphql/src/scripts/2024-05-05_compute_aggregated_results.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
import { Element, ElementType, PrismaClient } from '@klicker-uzh/prisma'
import { getInitialElementResults } from '@klicker-uzh/util'
import md5 from 'md5'
import {
QuestionResponseChoices,
QuestionResponseValue,
QuestionResultsChoices,
QuestionResultsOpen,
} from 'src/types/app'

async function run() {
const prisma = new PrismaClient()

const startTime = new Date().getTime()

let doContinue = true

while (doContinue) {
// fetch all question responses belonging to an element instance with type SC, MC, KPRIM, NUMERICAL or FREE_TEXT
const responses = await prisma.questionResponse.findMany({
where: {
// createdAt: {
// lte: new Date('2023-04-18T00:00:00.000Z'),
// },
isMigrated: false,
elementInstance: {
elementType: {
in: [
ElementType.SC,
ElementType.MC,
ElementType.KPRIM,
ElementType.NUMERICAL,
ElementType.FREE_TEXT,
],
},
},
},
include: {
elementInstance: {
include: { element: true },
},
},
orderBy: {
createdAt: 'desc',
},
take: 100,
})

if (responses.length === 0) {
doContinue = false
break
}

// intialize promises array and counters
let updatePromises: any[] = []
let missingDetailResponses = 0
let choicesQuestions = 0
let openQuestions = 0
let totalUpdates = 0

// loop over responses and update them as required
for (const response of responses) {
// const aggregatedResponses = response.aggregatedResponses

// if (aggregatedResponses !== null) {
// // console.log(
// // `Aggregated responses for questionResponse ${response.id} are not set to null`
// // )
// // updatePromises.push(
// // prisma.questionResponse.update({
// // where: { id: response.id },
// // data: {
// // isMigrated: true,
// // },
// // })
// // )
// continue
// }

const detailResponses = await prisma.questionResponseDetail.findMany({
where: {
elementInstanceId: response.elementInstanceId,
participantId: response.participantId,
},
})

if (detailResponses.length === 0) {
// console.log(
// `No detail responses found for questionResponse ${response.id}`
// )
const emptyAggregatedResponses = getInitialElementResults(
response.elementInstance!.element as Element
)
missingDetailResponses++

updatePromises.push(
prisma.questionResponse.update({
where: { id: response.id },
data: {
aggregatedResponses: emptyAggregatedResponses,
isMigrated: true,
},
})
)

continue
}

if (
response.elementInstance!.elementType === ElementType.SC ||
response.elementInstance!.elementType === ElementType.MC ||
response.elementInstance!.elementType === ElementType.KPRIM
) {
let aggregatedResponses = getInitialElementResults(
response.elementInstance!.element as Element
) as QuestionResultsChoices

// loop over the detail responses and update the aggregated responses
for (const detailResponse of detailResponses) {
aggregatedResponses.choices = (
detailResponse.response as QuestionResponseChoices
).choices.reduce(
(acc, ix) => ({
...acc,
[ix]: acc[ix] + 1,
}),
aggregatedResponses.choices
)
aggregatedResponses.total = aggregatedResponses.total + 1
}

updatePromises.push(
prisma.questionResponse.update({
where: { id: response.id },
data: {
aggregatedResponses: aggregatedResponses,
isMigrated: true,
},
})
)

// console.log(
// `NEW AGGREAGTED RESULTS FOR ${
// response.elementInstance!.elementType
// } QUESTION:`,
// aggregatedResponses
// )

choicesQuestions++
} else if (
response.elementInstance!.elementType === ElementType.NUMERICAL ||
response.elementInstance!.elementType === ElementType.FREE_TEXT
) {
let aggregatedResponses = getInitialElementResults(
response.elementInstance!.element as Element
) as QuestionResultsOpen

// loop over the detail responses and update the aggregated responses
for (const detailResponse of detailResponses) {
const value = (detailResponse.response as QuestionResponseValue).value
const hashValue = md5(value)
aggregatedResponses.responses[hashValue] = {
value: value,
count: (aggregatedResponses.responses[hashValue]?.count ?? 0) + 1,
}
aggregatedResponses.total = aggregatedResponses.total + 1
}

updatePromises.push(
prisma.questionResponse.update({
where: { id: response.id },
data: {
aggregatedResponses: aggregatedResponses,
isMigrated: true,
},
})
)

// console.log(
// `NEW AGGREAGTED RESULTS FOR ${
// response.elementInstance!.elementType
// } QUESTION:`,
// aggregatedResponses
// )

openQuestions++
} else {
throw new Error(
`Unknown element type for this script ${
response.elementInstance!.elementType
}`
)
}
}

await Promise.allSettled(updatePromises)

// logging
console.warn(`Time elapsed: ${(new Date().getTime() - startTime) / 1000}`)
console.log(
`Total considered question responses with inconsistent aggregated results: ${responses.length}`
)
console.log(`Missing detail responses: ${missingDetailResponses}`)
console.log(`Total updates: ${totalUpdates}`)
console.log(`Choices question updates: ${choicesQuestions}`)
console.log(`Open question updates: ${openQuestions}`)
}
}

await run()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "QuestionResponse" ADD COLUMN "isMigrated" BOOLEAN NOT NULL DEFAULT false;
2 changes: 2 additions & 0 deletions packages/prisma/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,8 @@ model QuestionResponse {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
isMigrated Boolean @default(false)
@@unique([participantId, questionInstanceId])
@@unique([participantId, elementInstanceId])
}
Expand Down
Loading

0 comments on commit a03b927

Please sign in to comment.