1
- const rules = {
2
- timesInARow : ( times ) => ( items , field ) => {
1
+ /****** CONFIGURATION FOR PAUSING RULES ******/
2
+ /**
3
+ * `fields` describe per-field rules in a format
4
+ * <field-name>: [<rule>(<optional params for the rule>)]
5
+ * `global` is for rules applied to the whole annotation
6
+ */
7
+ const RULES = {
8
+ fields : {
9
+ comment : [ timesInARow ( 3 ) ] ,
10
+ sentiment : [ tooSimilar ( ) ] ,
11
+ } ,
12
+ global : [ tooFast ( ) ] ,
13
+ }
14
+ /**
15
+ * Messages for users when they are paused.
16
+ * Each message is a function with the same name as original rule and it receives an object with
17
+ * `items` and `field`.
18
+ */
19
+ const MESSAGES = {
20
+ timesInARow : ( { field } ) => `Too many similar values for ${ field } ` ,
21
+ tooSimilar : ( { field } ) => `Too similar values for ${ field } ` ,
22
+ tooFast : ( ) => `Too fast annotations` ,
23
+ }
24
+
25
+
26
+
27
+ /****** ALL AVAILABLE RULES ******/
28
+ /**
29
+ * They recieve params and return function which recieves `items` and optional `field`.
30
+ * If condition is met it returns warning message. If not — returns `false`.
31
+ */
32
+
33
+ // check if values for the `field` in last `times` items are the same
34
+ function timesInARow ( times ) {
35
+ return ( items , field ) => {
3
36
if ( items . length < times ) return false
4
37
const last = String ( items . at ( - 1 ) . values [ field ] )
5
38
return items . slice ( - times ) . every ( ( item ) => String ( item . values [ field ] ) === last )
6
- ? `Too many similar values for ${ field } `
39
+ ? MESSAGES . timesInARow ( { items , field } )
7
40
: false
8
- } ,
9
- tooSimilar : ( deviation = 0.1 , max_count = 10 ) => ( items , field ) => {
41
+ } ;
42
+ }
43
+ function tooSimilar ( deviation = 0.1 , max_count = 10 ) {
44
+ return ( items , field ) => {
10
45
if ( items . length < max_count ) return false
11
46
const values = items . map ( ( item ) => item . values [ field ] )
12
47
const points = values . map ( ( v ) => values . indexOf ( v ) )
13
48
return calcDeviation ( points ) < deviation
14
- ? `Too similar values for ${ field } `
49
+ ? MESSAGES . tooSimilar ( { items , field } )
15
50
: false
16
- } ,
17
- tooFast : ( minutes = 10 , times = 20 ) => ( items ) => {
51
+ } ;
52
+ }
53
+ function tooFast ( minutes = 10 , times = 20 ) {
54
+ return ( items ) => {
18
55
if ( items . length < times ) return false
19
56
const last = items . at ( - 1 )
20
57
const first = items . at ( - times )
21
58
return last . created_at - first . created_at < minutes * 60
22
- ? `Too fast annotations`
59
+ ? MESSAGES . tooFast ( { items } )
23
60
: false
24
- }
61
+ } ;
25
62
}
26
63
27
- /****** RULES FOR SUBMITTED ANNOTATIONS ******/
28
- const RULES = {
29
- fields : {
30
- comment : [ rules . timesInARow ( 3 ) ] ,
31
- sentiment : [ rules . tooSimilar ( ) ] ,
32
- } ,
33
- global : [ rules . tooFast ( ) ] ,
34
- }
35
64
65
+
66
+ /****** INTERNAL CODE ******/
36
67
const project = DM . project . id
37
68
if ( ! DM . project ) return ;
38
69
@@ -43,6 +74,7 @@ const values = Object.fromEntries(fields.map(
43
74
( field ) => [ field , DM . project . parsed_label_config [ field ] ?. labels ] ,
44
75
) )
45
76
77
+ // simplified version of MSE with normalized x-axis
46
78
function calcDeviation ( data ) {
47
79
const n = data . length ;
48
80
// we normalize indices from -n/2 to n/2 so meanX is 0
@@ -72,9 +104,10 @@ LSI.on("submitAnnotation", (_store, ann) => {
72
104
stats . push ( { values, created_at : Date . now ( ) / 1000 } )
73
105
74
106
for ( const rule of RULES . global ) {
75
- if ( rule ( stats ) ) {
107
+ const result = rule ( stats )
108
+ if ( result ) {
76
109
localStorage . setItem ( key , "[]" ) ;
77
- pause ( "Wow, cowboy, not so fast!" ) ;
110
+ pause ( result ) ;
78
111
return ;
79
112
}
80
113
}
0 commit comments