3
3
# Copyright (C) 2015-2020 Picheral, Colin, Irisson (UPMC-CNRS)
4
4
#
5
5
import logging
6
+ import pytest
6
7
7
8
from typing import List
8
9
from API_models .filters import ProjectFilters , ProjectFiltersDict
@@ -30,6 +31,7 @@ def _prj_query(fastapi, auth, prj_id, **kwargs) -> List[int]:
30
31
OBJECT_SET_DELETE_URL = "/object_set/"
31
32
OBJECT_SET_SUMMARY_URL = "/object_set/{project_id}/summary?only_total=False"
32
33
OBJECT_SET_PARENTS_URL = "/object_set/parents"
34
+ OBJECT_QUERY_URL = "/object/{object_id}"
33
35
34
36
PROJECT_SET_USER_STATS = "/project_set/user_stats?ids={prj_ids}"
35
37
@@ -61,15 +63,43 @@ def classify_all(fastapi, obj_ids, classif_id):
61
63
assert rsp .status_code == status .HTTP_200_OK
62
64
63
65
64
- def classify_auto_all (fastapi , obj_ids , classif_id ):
66
+ def classify_auto_all (fastapi , obj_ids , classif_id , scores = None ):
65
67
url = OBJECT_SET_CLASSIFY_AUTO_URL
66
68
classifications = [classif_id for _obj in obj_ids ]
67
- scores = [0.52 for _obj in obj_ids ]
69
+ if not scores :
70
+ scores = [0.52 for _obj in obj_ids ]
68
71
rsp = fastapi .post (url , headers = ADMIN_AUTH , json = {"target_ids" : obj_ids ,
69
72
"classifications" : classifications ,
70
73
"scores" : scores ,
71
74
"keep_log" : True })
72
75
assert rsp .status_code == status .HTTP_200_OK
76
+
77
+
78
+ def classify_auto_incorrect (fastapi , obj_ids ):
79
+ url = OBJECT_SET_CLASSIFY_AUTO_URL
80
+ classifications = [- 1 for _obj in obj_ids ]
81
+
82
+ # List of scores of a different length, should raise an error
83
+ scores = [0.1 for _obj in obj_ids [:- 1 ]]
84
+ with pytest .raises (AssertionError ):
85
+ rsp = fastapi .post (url , headers = ADMIN_AUTH , json = {"target_ids" : obj_ids ,
86
+ "classifications" : classifications ,
87
+ "scores" : scores ,
88
+ "keep_log" : True })
89
+ # List of scores outside [0, 1], should raise an error
90
+ scores = [2. for _obj in obj_ids ]
91
+ with pytest .raises (AssertionError ):
92
+ rsp = fastapi .post (url , headers = ADMIN_AUTH , json = {"target_ids" : obj_ids ,
93
+ "classifications" : classifications ,
94
+ "scores" : scores ,
95
+ "keep_log" : True })
96
+ # List of scores with wrong type, should fail
97
+ scores = [None for _obj in obj_ids ]
98
+ rsp = fastapi .post (url , headers = ADMIN_AUTH , json = {"target_ids" : obj_ids ,
99
+ "classifications" : classifications ,
100
+ "scores" : scores ,
101
+ "keep_log" : True })
102
+ assert rsp .status_code == status .HTTP_422_UNPROCESSABLE_ENTITY
73
103
74
104
75
105
# Note: to go faster in a local dev environment, use "filled_database" instead of "database" below
@@ -179,9 +209,26 @@ def get_object_set_stats():
179
209
rsp = fastapi .post (url , headers = ADMIN_AUTH , json = {})
180
210
assert rsp .status_code == status .HTTP_200_OK
181
211
212
+ # Incorrect ML results
213
+ classify_auto_incorrect (fastapi , obj_ids [:4 ])
214
+
182
215
# Super ML result, 4 first objects are crustacea
183
216
classify_auto_all (fastapi , obj_ids [:4 ], crustacea )
184
217
218
+ assert get_stats (fastapi , prj_id ) == {'nb_dubious' : 0 ,
219
+ 'nb_predicted' : 4 ,
220
+ 'nb_unclassified' : 4 ,
221
+ 'nb_validated' : 0 ,
222
+ 'projid' : prj_id ,
223
+ 'used_taxa' : [- 1 , crustacea ]}
224
+
225
+ # New ML results with a different score for the second object
226
+ classify_auto_all (fastapi , [obj_ids [1 ]], crustacea , [0.8 ])
227
+ url = OBJECT_QUERY_URL .format (object_id = obj_ids [1 ])
228
+ rsp = fastapi .get (url , headers = ADMIN_AUTH )
229
+ assert rsp .status_code == status .HTTP_200_OK
230
+ assert rsp .json ()['classif_auto_score' ] == 0.8
231
+
185
232
assert get_stats (fastapi , prj_id ) == {'nb_dubious' : 0 ,
186
233
'nb_predicted' : 4 ,
187
234
'nb_unclassified' : 4 ,
@@ -198,16 +245,44 @@ def get_object_set_stats():
198
245
'nb_unclassified' : 0 ,
199
246
'nb_validated' : 8 ,
200
247
'projid' : prj_id ,
201
- 'used_taxa' : [25828 ]} # No more Unclassified and Copepod is in +
248
+ 'used_taxa' : [copepod_id ]} # No more Unclassified and Copepod is in +
202
249
203
250
# No history yet as the object was just created
204
251
classif = classif_history (fastapi , obj_ids [0 ])
205
252
assert len (classif ) == 1
206
253
assert classif [0 ]['classif_date' ] is not None # e.g. 2021-09-12T09:28:03.278626
207
254
classif [0 ]['classif_date' ] = "now"
208
255
assert classif == [
209
- {'objid' : obj_ids [0 ], 'classif_id' : 12846 , 'classif_date' : 'now' , 'classif_who' : None ,
256
+ {'objid' : obj_ids [0 ], 'classif_id' : crustacea , 'classif_date' : 'now' , 'classif_who' : None ,
210
257
'classif_type' : 'A' , 'classif_qual' : 'P' , 'classif_score' : 0.52 , 'user_name' : None , 'taxon_name' : 'Crustacea' }]
258
+
259
+ # Revert on validated objects
260
+ url = OBJECT_SET_REVERT_URL .format (project_id = prj_id , dry_run = False , tgt_usr = "" )
261
+ rsp = fastapi .post (url , headers = ADMIN_AUTH , json = {})
262
+ assert rsp .status_code == status .HTTP_200_OK
263
+ stats = rsp .json ()
264
+
265
+ assert get_stats (fastapi , prj_id ) == {'nb_dubious' : 0 ,
266
+ 'nb_predicted' : 4 ,
267
+ 'nb_unclassified' : 4 ,
268
+ 'nb_validated' : 0 ,
269
+ 'projid' : prj_id ,
270
+ 'used_taxa' : [- 1 , crustacea ]}
271
+
272
+ # Second revert, should not change since the last record in history is the same
273
+ rsp = fastapi .post (url , headers = ADMIN_AUTH , json = {})
274
+ assert rsp .status_code == status .HTTP_200_OK
275
+ stats = rsp .json ()
276
+
277
+ assert get_stats (fastapi , prj_id ) == {'nb_dubious' : 0 ,
278
+ 'nb_predicted' : 4 ,
279
+ 'nb_unclassified' : 4 ,
280
+ 'nb_validated' : 0 ,
281
+ 'projid' : prj_id ,
282
+ 'used_taxa' : [- 1 , crustacea ]}
283
+
284
+ # Apply validation again after revert
285
+ classify_all (fastapi , obj_ids , copepod_id )
211
286
212
287
# Not a copepod :(
213
288
classify_all (fastapi , obj_ids , entomobryomorpha_id )
@@ -228,7 +303,7 @@ def classify_all_no_change(classif_id):
228
303
classif2 [0 ]['classif_date' ] = 'hopefully just now'
229
304
classif2 [1 ]['classif_date' ] = 'a bit before'
230
305
assert classif2 == [{'classif_date' : 'hopefully just now' ,
231
- 'classif_id' : 25828 ,
306
+ 'classif_id' : copepod_id ,
232
307
'classif_qual' : 'V' ,
233
308
'classif_score' : None ,
234
309
'classif_type' : 'M' ,
@@ -237,7 +312,7 @@ def classify_all_no_change(classif_id):
237
312
'taxon_name' : 'Copepoda' ,
238
313
'user_name' : 'Application Administrator' },
239
314
{'classif_date' : 'a bit before' ,
240
- 'classif_id' : 12846 ,
315
+ 'classif_id' : crustacea ,
241
316
'classif_qual' : 'P' ,
242
317
'classif_score' : 0.52 ,
243
318
'classif_type' : 'A' ,
@@ -261,9 +336,35 @@ def classify_all_no_change(classif_id):
261
336
'nb_unclassified' : 0 ,
262
337
'nb_validated' : 8 ,
263
338
'projid' : prj_id ,
264
- 'used_taxa' : [
265
- 25835 ]}] # <- copepod is gone, unclassified as well, replaced with entomobryomorpha
266
-
339
+ 'used_taxa' :
340
+ [entomobryomorpha_id ]}] # <- copepod is gone, unclassified as well, replaced with entomobryomorpha
341
+
342
+ # Reset to predicted on validated objects
343
+ url = OBJECT_SET_RESET_PREDICTED_URL .format (project_id = prj_id )
344
+ rsp = fastapi .post (url , headers = ADMIN_AUTH , json = {})
345
+ assert rsp .status_code == status .HTTP_200_OK
346
+ stats = rsp .json ()
347
+
348
+ assert get_stats (fastapi , prj_id ) == {'nb_dubious' : 0 ,
349
+ 'nb_predicted' : 8 ,
350
+ 'nb_unclassified' : 0 ,
351
+ 'nb_validated' : 0 ,
352
+ 'projid' : prj_id ,
353
+ 'used_taxa' : [entomobryomorpha_id ]}
354
+
355
+ # Revert after reset to predicted
356
+ url = OBJECT_SET_REVERT_URL .format (project_id = prj_id , dry_run = False , tgt_usr = "" )
357
+ rsp = fastapi .post (url , headers = ADMIN_AUTH , json = {})
358
+ assert rsp .status_code == status .HTTP_200_OK
359
+ stats = rsp .json ()
360
+
361
+ assert get_stats (fastapi , prj_id ) == {'nb_dubious' : 0 ,
362
+ 'nb_predicted' : 0 ,
363
+ 'nb_unclassified' : 0 ,
364
+ 'nb_validated' : 8 ,
365
+ 'projid' : prj_id ,
366
+ 'used_taxa' : [entomobryomorpha_id ]}
367
+
267
368
# Delete some object via API, why not?
268
369
rsp = fastapi .delete (OBJECT_SET_DELETE_URL , headers = ADMIN_AUTH , json = obj_ids [:4 ])
269
370
assert rsp .status_code == status .HTTP_200_OK
@@ -285,7 +386,7 @@ def classify_all_no_change(classif_id):
285
386
ref_stats = [{"projid" : prj_id ,
286
387
"annotators" : [{"id" : 1 ,
287
388
"name" : "Application Administrator" }],
288
- "activities" : [{"id" : 1 , "nb_actions" : 8 ,
389
+ "activities" : [{"id" : 1 , "nb_actions" : 12 ,
289
390
"last_annot" : "2022-05-12T14:21:15" }]}]
290
391
# Fix the date on both sides
291
392
ref_stats [0 ]["activities" ][0 ]["last_annot" ] = "FIXED DATE"
0 commit comments