/
systemParticipationCodeItemImpacts.go
188 lines (164 loc) · 5.86 KB
/
systemParticipationCodeItemImpacts.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
package reports
import (
"encoding/csv"
"fmt"
"strconv"
"github.com/nsip/dev-nrt/helper"
"github.com/nsip/dev-nrt/records"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
type SystemParticipationCodeItemImpacts struct {
baseReport // embed common setup capability
cfh helper.CodeframeHelper
}
//
// Reports errors when response items contain unexpected information based on the
// participation code
//
func SystemParticipationCodeItemImpactsReport(cfh helper.CodeframeHelper) *SystemParticipationCodeItemImpacts {
r := SystemParticipationCodeItemImpacts{cfh: cfh}
r.initialise("./config/SystemParticipationCodeItemImpacts.toml")
r.printStatus()
return &r
}
//
// implement the EventPipe interface, core work of the
// report engine.
//
func (r *SystemParticipationCodeItemImpacts) ProcessEventRecords(in chan *records.EventOrientedRecord) chan *records.EventOrientedRecord {
out := make(chan *records.EventOrientedRecord)
go func() {
defer close(out)
// open the csv file writer, and set the header
w := csv.NewWriter(r.outF)
defer r.outF.Close()
w.Write(r.config.header)
defer w.Flush()
for eor := range in {
if !r.config.activated { // only process if activated
out <- eor
continue
}
//
// single event record can produce multiple item errors
//
for _, itemError := range participationItemImpact(eor) {
//
// generate any calculated fields required
//
eor.CalculatedFields = r.calculateFields(eor, itemError)
//
// now loop through the ouput definitions to create a
// row of results
//
var result string
var row []string = make([]string, 0, len(r.config.queries))
for _, query := range r.config.queries {
result = eor.GetValueString(query)
row = append(row, result)
}
// write the row to the output file
if err := w.Write(row); err != nil {
fmt.Println("Warning: error writing record to csv:", r.config.name, err)
}
}
out <- eor
}
}()
return out
}
//
// checks for anomalies in the record.
//
// same checks as n2.
//
func participationItemImpact(eor *records.EventOrientedRecord) []*itemError {
//
// fetch properties from record
//
participationCode := eor.GetValueString("NAPEventStudentLink.ParticipationCode")
testDomain := eor.GetValueString("NAPTest.TestContent.Domain")
//
// now iterate testlets -> items in response
//
itemErrors := make([]*itemError, 0)
gjson.GetBytes(eor.NAPStudentResponseSet, "NAPStudentResponseSet.TestletList.Testlet").
ForEach(func(key, value gjson.Result) bool {
ierr := &itemError{}
//
// now iterate testlet item responses
//
value.Get("ItemResponseList.ItemResponse").
ForEach(func(key, value gjson.Result) bool {
// capture the item refid
ierr.itemRefId = value.Get("NAPTestItemRefId").String()
// and the json of the item response
ierr.itemResponseJson = value.Raw
// properties used for logic tests
itemLapsedTime := value.Get("LapsedTimeItem").String()
itemScore := value.Get("Score").String()
itemResponse := value.Get("Response").String()
itemSubScores := value.Get("SubscoreList.Subscore").Array()
itemscore_num, _ := strconv.Atoi(itemScore)
//
// validation logic copied from n2 implementation
//
// note: logic using testletscore has not been ported
// as this score n longer exists in dataset
//
if itemLapsedTime == "" || itemResponse == "" {
if participationCode != "P" && participationCode != "S" && !(testDomain == "Writing" && participationCode == "F") {
ierr.err = errUnexpectedItemResponse
itemErrors = append(itemErrors, ierr)
}
}
if (itemScore != "" || len(itemSubScores) > 0) &&
participationCode != "P" && participationCode != "R" && !(testDomain == "Writing" && participationCode == "F") {
ierr.err = errUnexpectedItemScore
itemErrors = append(itemErrors, ierr)
}
if ((itemScore != "" && itemscore_num != 0) || len(itemSubScores) > 0) &&
participationCode == "R" {
ierr.err = errNonZeroItemScore
itemErrors = append(itemErrors, ierr)
}
//
if itemScore == "" {
if participationCode == "R" || participationCode == "P" || (testDomain == "Writing" && participationCode == "F") {
ierr.err = errMissingItemScore
itemErrors = append(itemErrors, ierr)
}
}
//
if testDomain == "Writing" && (participationCode == "P" || participationCode == "F") {
if len(itemSubScores) == 0 {
ierr.err = errMissingItemWritingScore
itemErrors = append(itemErrors, ierr)
}
}
//
return true // keep iterating, move on to next item response
})
return true // keep iterating
})
return itemErrors
}
//
// generates a block of json that can be added to the
// record containing values that are not in the original data
//
//
func (r *SystemParticipationCodeItemImpacts) calculateFields(eor *records.EventOrientedRecord, ierr *itemError) []byte {
json := eor.CalculatedFields // maintain existing calculated fields
itemLocalId := r.cfh.GetCodeframeObjectValueString(ierr.itemRefId, "NAPTestItem.TestItemContent.NAPTestItemLocalId")
itemName := r.cfh.GetCodeframeObjectValueString(ierr.itemRefId, "NAPTestItem.TestItemContent.ItemName")
itemScore := gjson.Get(ierr.itemResponseJson, "Score").String()
itemSubScores := gjson.Get(ierr.itemResponseJson, "SubscoreList.Subscore").String()
json, _ = sjson.SetBytes(json, "CalculatedFields.TestItem.TestItemContent.NAPTestItemLocalId", itemLocalId)
json, _ = sjson.SetBytes(json, "CalculatedFields.TestItem.TestItemContent.ItemName", itemName)
json, _ = sjson.SetBytes(json, "CalculatedFields.ItemResponse.Score", itemScore)
json, _ = sjson.SetBytes(json, "CalculatedFields.ItemResponse.Subscores", itemSubScores)
json, _ = sjson.SetBytes(json, "CalculatedFields.ParticipationCodeItemImpactError", ierr.err.Error())
return json
}