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 ) => {
336 if ( items . length < times ) return false
437 const last = String ( items . at ( - 1 ) . values [ field ] )
538 return items . slice ( - times ) . every ( ( item ) => String ( item . values [ field ] ) === last )
6- ? `Too many similar values for ${ field } `
39+ ? MESSAGES . timesInARow ( { items , field } )
740 : 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 ) => {
1045 if ( items . length < max_count ) return false
1146 const values = items . map ( ( item ) => item . values [ field ] )
1247 const points = values . map ( ( v ) => values . indexOf ( v ) )
1348 return calcDeviation ( points ) < deviation
14- ? `Too similar values for ${ field } `
49+ ? MESSAGES . tooSimilar ( { items , field } )
1550 : false
16- } ,
17- tooFast : ( minutes = 10 , times = 20 ) => ( items ) => {
51+ } ;
52+ }
53+ function tooFast ( minutes = 10 , times = 20 ) {
54+ return ( items ) => {
1855 if ( items . length < times ) return false
1956 const last = items . at ( - 1 )
2057 const first = items . at ( - times )
2158 return last . created_at - first . created_at < minutes * 60
22- ? `Too fast annotations`
59+ ? MESSAGES . tooFast ( { items } )
2360 : false
24- }
61+ } ;
2562}
2663
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- }
3564
65+
66+ /****** INTERNAL CODE ******/
3667const project = DM . project . id
3768if ( ! DM . project ) return ;
3869
@@ -43,6 +74,7 @@ const values = Object.fromEntries(fields.map(
4374 ( field ) => [ field , DM . project . parsed_label_config [ field ] ?. labels ] ,
4475) )
4576
77+ // simplified version of MSE with normalized x-axis
4678function calcDeviation ( data ) {
4779 const n = data . length ;
4880 // we normalize indices from -n/2 to n/2 so meanX is 0
@@ -72,9 +104,10 @@ LSI.on("submitAnnotation", (_store, ann) => {
72104 stats . push ( { values, created_at : Date . now ( ) / 1000 } )
73105
74106 for ( const rule of RULES . global ) {
75- if ( rule ( stats ) ) {
107+ const result = rule ( stats )
108+ if ( result ) {
76109 localStorage . setItem ( key , "[]" ) ;
77- pause ( "Wow, cowboy, not so fast!" ) ;
110+ pause ( result ) ;
78111 return ;
79112 }
80113 }
0 commit comments