From dd892116ab670f60020670ec2276a89c8ab9eb18 Mon Sep 17 00:00:00 2001 From: Antoine Jeanneney <29945628+ajeanneney@users.noreply.github.com> Date: Tue, 22 Oct 2024 10:17:12 +0200 Subject: [PATCH] Feat/new checklist (#133) * create new annotationReport type * handle only new annotationReport * save checklist only if not empty * extract checklist rendering * add viewer mode for checklist * put chekclist in accordion * make check clickable * show lines of concerned selected check * diplay context lines for checklist * styling * add checklist header * eslint * simplify annotationReportType * fix test * change 'label' to 'category' in nlp response * update storage-example * review changes --------- Co-authored-by: Antoine Jeanneney --- .../src/annotator/fetcher/api/nlpApiType.ts | 6 +- .../src/annotator/fetcher/api/nlpLocalApi.ts | 2 +- .../fetcher/mapper/nlpMapper.spec.ts | 133 +++++++++++++- .../src/annotator/fetcher/mapper/nlpMapper.ts | 4 +- .../src/annotator/fetcher/nlpFetcher.spec.ts | 2 +- .../test/generator/nlpAnnotationsGenerator.ts | 2 +- .../storage-example/annotations/123452.json | 1 - .../storage-example/annotations/123456.json | 165 +++++++++++------- .../storage-example/annotations/123457.json | 1 - .../storage-example/documents/123452.json | 11 +- .../storage-example/documents/123456.json | 6 +- .../storage-example/documents/123457.json | 12 +- .../src/lib/annotator/buildAnnotator.ts | 23 +-- packages/generic/client/package.json | 2 +- .../AnnotationsPanel/AnnotationsPanel.tsx | 33 +--- .../AnnotationsPanel/Checklist.tsx | 77 ++++++++ .../AnnotationsPanel/ChecklistEntry.tsx | 63 +++++++ .../useChecklistEntryHandler.ts | 93 ++++++++++ .../AnnotationsPanel/useEntityEntryHandler.ts | 2 +- .../DocumentPanelHeader/ChecklistHeader.tsx | 59 +++++++ .../DocumentPanelHeader.tsx | 4 + .../DocumentPanel/DocumentViewer.tsx | 56 +++--- .../lib/getAnnotationTextDisplayStyle.ts | 2 + ...cumentViewerModeHandlerContextProvider.tsx | 1 + .../buildDocumentViewerModeHandler.ts | 13 +- .../services/documentViewerMode/viewerMode.ts | 8 +- packages/generic/client/src/wordings/fr.ts | 2 +- .../annotationReport/annotationReportType.ts | 52 +++++- yarn.lock | 4 +- 29 files changed, 679 insertions(+), 160 deletions(-) create mode 100644 packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/Checklist.tsx create mode 100644 packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/ChecklistEntry.tsx create mode 100644 packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/useChecklistEntryHandler.ts create mode 100644 packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentPanelHeader/ChecklistHeader.tsx diff --git a/packages/courDeCassation/src/annotator/fetcher/api/nlpApiType.ts b/packages/courDeCassation/src/annotator/fetcher/api/nlpApiType.ts index fa65d1f17..4e6146944 100644 --- a/packages/courDeCassation/src/annotator/fetcher/api/nlpApiType.ts +++ b/packages/courDeCassation/src/annotator/fetcher/api/nlpApiType.ts @@ -1,5 +1,5 @@ import { labelTreatmentsType } from 'sder'; -import { documentType, settingsType } from '@label/core'; +import { annotationReportType, documentType, settingsType } from '@label/core'; export type { nlpApiType, nlpResponseType, nlpLossType, nlpVersion }; @@ -29,7 +29,7 @@ type nlpVersion = { type nlpResponseType = { entities: nlpAnnotationType[]; - checklist: string[]; + checklist?: annotationReportType['checklist']; newCategoriesToAnnotate?: string[]; newCategoriesToUnAnnotate?: string[]; additionalTermsToAnnotate?: string[]; @@ -42,7 +42,7 @@ type nlpAnnotationType = { text: string; start: number; end: number; - label: string; + category: string; source: string; score: number; entityId: string; diff --git a/packages/courDeCassation/src/annotator/fetcher/api/nlpLocalApi.ts b/packages/courDeCassation/src/annotator/fetcher/api/nlpLocalApi.ts index 633496207..69295ca8c 100644 --- a/packages/courDeCassation/src/annotator/fetcher/api/nlpLocalApi.ts +++ b/packages/courDeCassation/src/annotator/fetcher/api/nlpLocalApi.ts @@ -37,7 +37,7 @@ function buildNlpLocalApi(): nlpApiType { return { ...annotations, entities: annotations.entities.filter((entity) => - availableCategories.includes(entity.label), + availableCategories.includes(entity.category), ), checklist: annotations.checklist, newCategoriesToAnnotate: annotations.newCategoriesToAnnotate, diff --git a/packages/courDeCassation/src/annotator/fetcher/mapper/nlpMapper.spec.ts b/packages/courDeCassation/src/annotator/fetcher/mapper/nlpMapper.spec.ts index 0012de7a0..52ceec717 100644 --- a/packages/courDeCassation/src/annotator/fetcher/mapper/nlpMapper.spec.ts +++ b/packages/courDeCassation/src/annotator/fetcher/mapper/nlpMapper.spec.ts @@ -26,7 +26,7 @@ const nlpAnnotations: nlpResponseType = { text: 'ANNOTATION1', start: 0, end: 11, - label: 'LABEL1', + category: 'LABEL1', source: 'NLP', score: 0.5, entityId: 'LABEL1_annotation1', @@ -35,13 +35,13 @@ const nlpAnnotations: nlpResponseType = { text: 'ANNOTATION2', start: 12, end: 23, - label: 'LABEL2', + category: 'LABEL2', source: 'NLP', score: 0.6, entityId: 'LABEL2_annotation2', }, ], - checklist: ['CHECK 1', 'CHECK 2'], + checklist: [], versions: nlpVersion, }; @@ -51,7 +51,7 @@ const nlpAnnotationsWithAdditionalTerms: nlpResponseType = { text: 'ANNOTATION1', start: 0, end: 11, - label: 'LABEL1', + category: 'LABEL1', source: 'NLP', score: 0.5, entityId: 'LABEL1_annotation1', @@ -60,13 +60,80 @@ const nlpAnnotationsWithAdditionalTerms: nlpResponseType = { text: 'ANNOTATION2', start: 12, end: 23, - label: 'LABEL2', + category: 'LABEL2', source: 'NLP', score: 0.6, entityId: 'LABEL2_annotation2', }, ], - checklist: ['CHECK 1', 'CHECK 2'], + checklist: [], + additionalTermsToUnAnnotate: ['blabla', 'toto'], + versions: nlpVersion, +}; + +const nlpAnnotationsWithChecklist: nlpResponseType = { + entities: [ + { + text: 'ANNOTATION1', + start: 0, + end: 11, + category: 'LABEL1', + source: 'NLP', + score: 0.5, + entityId: 'LABEL1_annotation1', + }, + ], + checklist: [ + { + checkType: 'missing_something', + message: "Label est-il un bon logiciel d'annotation ?", + entities: [ + { + text: 'Label', + start: 0, + end: 5, + category: 'myCategory', + source: 'source1', + score: 0.85, + entityId: 'myCategory', + }, + { + text: 'Application', + start: 10, + end: 15, + category: 'myCategory', + source: 'source2', + score: 0.9, + entityId: 'myCategory_application', + }, + ], + sentences: [ + { + start: 0, + end: 50, + }, + ], + metadata_text: ['Label', 'Applcation'], + }, + { + checkType: 'other', + message: + "L'annotation [Antoine] est présente dans les catégories [développeur, data scientist] est-ce une erreur ?", + entities: [ + { + text: 'Antoine', + start: 20, + end: 25, + category: 'developpeur', + source: 'nlp', + score: 1, + entityId: 'developpeur_antoine', + }, + ], + sentences: undefined, + metadata_text: undefined, + }, + ], additionalTermsToUnAnnotate: ['blabla', 'toto'], versions: nlpVersion, }; @@ -102,12 +169,62 @@ describe('nlpMapper', () => { describe('mapNlpAnnotationstoReport', () => { it('should convert the nlp annotations into an annotation report', () => { const annotationReport = nlpMapper.mapNlpAnnotationstoReport( - nlpAnnotations, + nlpAnnotationsWithChecklist, document, ); expect(annotationReport).toEqual({ - checklist: ['CHECK 1', 'CHECK 2'], + checklist: [ + { + checkType: 'missing_something', + message: "Label est-il un bon logiciel d'annotation ?", + entities: [ + { + text: 'Label', + start: 0, + end: 5, + category: 'myCategory', + source: 'source1', + score: 0.85, + entityId: 'myCategory', + }, + { + text: 'Application', + start: 10, + end: 15, + category: 'myCategory', + source: 'source2', + score: 0.9, + entityId: 'myCategory_application', + }, + ], + sentences: [ + { + start: 0, + end: 50, + }, + ], + metadata_text: ['Label', 'Applcation'], + }, + { + checkType: 'other', + message: + "L'annotation [Antoine] est présente dans les catégories [développeur, data scientist] est-ce une erreur ?", + entities: [ + { + text: 'Antoine', + start: 20, + end: 25, + category: 'developpeur', + source: 'nlp', + score: 1, + entityId: 'developpeur_antoine', + }, + ], + sentences: undefined, + metadata_text: undefined, + }, + ], documentId: document._id, _id: annotationReport._id, }); diff --git a/packages/courDeCassation/src/annotator/fetcher/mapper/nlpMapper.ts b/packages/courDeCassation/src/annotator/fetcher/mapper/nlpMapper.ts index 74e103487..dd52cf0bd 100644 --- a/packages/courDeCassation/src/annotator/fetcher/mapper/nlpMapper.ts +++ b/packages/courDeCassation/src/annotator/fetcher/mapper/nlpMapper.ts @@ -21,7 +21,7 @@ function mapNlpAnnotationsToAnnotations( ): annotationType[] { return nlpAnnotations.entities.map((nlpAnnotation) => annotationModule.lib.buildAnnotation({ - category: nlpAnnotation.label, + category: nlpAnnotation.category, entityId: nlpAnnotation.entityId, start: nlpAnnotation.start, certaintyScore: nlpAnnotation.score, @@ -35,7 +35,7 @@ function mapNlpAnnotationstoReport( document: documentType, ): annotationReportType { return annotationReportModule.lib.buildAnnotationReport({ - checklist: nlpAnnotations.checklist, + checklist: nlpAnnotations.checklist ?? [], documentId: document._id, }); } diff --git a/packages/courDeCassation/src/annotator/fetcher/nlpFetcher.spec.ts b/packages/courDeCassation/src/annotator/fetcher/nlpFetcher.spec.ts index 8c4c15077..366f8f9de 100644 --- a/packages/courDeCassation/src/annotator/fetcher/nlpFetcher.spec.ts +++ b/packages/courDeCassation/src/annotator/fetcher/nlpFetcher.spec.ts @@ -39,7 +39,7 @@ describe('nlpFetcher', () => { annotations.some( (annotation) => annotation.start === nlpAnnotation.start && - annotation.category === nlpAnnotation.label, + annotation.category === nlpAnnotation.category, ), ).toEqual(true); }); diff --git a/packages/courDeCassation/src/annotator/test/generator/nlpAnnotationsGenerator.ts b/packages/courDeCassation/src/annotator/test/generator/nlpAnnotationsGenerator.ts index cd8335d47..34b368930 100644 --- a/packages/courDeCassation/src/annotator/test/generator/nlpAnnotationsGenerator.ts +++ b/packages/courDeCassation/src/annotator/test/generator/nlpAnnotationsGenerator.ts @@ -24,7 +24,7 @@ function generateRandomNlpAnnotation() { start: start, end: start + random(8), score: Math.random(), - label: `LABEL`, + category: `LABEL`, source: `NLP`, entityId: `LABEL_${text.toLowerCase()}`, }; diff --git a/packages/courDeCassation/storage-example/annotations/123452.json b/packages/courDeCassation/storage-example/annotations/123452.json index 5beea2e90..47095c70e 100644 --- a/packages/courDeCassation/storage-example/annotations/123452.json +++ b/packages/courDeCassation/storage-example/annotations/123452.json @@ -1,5 +1,4 @@ { "entities": [], - "check_needed": false, "checklist": [] } diff --git a/packages/courDeCassation/storage-example/annotations/123456.json b/packages/courDeCassation/storage-example/annotations/123456.json index 9c785dec0..cd8456998 100644 --- a/packages/courDeCassation/storage-example/annotations/123456.json +++ b/packages/courDeCassation/storage-example/annotations/123456.json @@ -1,7 +1,7 @@ { "entities": [ { - "label": "localite", + "category": "localite", "start": 37, "end": 46, "text": "Dunkerque", @@ -9,7 +9,7 @@ "score": 1 }, { - "label": "localite", + "category": "localite", "start": 812, "end": 821, "text": "Dunkerque", @@ -17,7 +17,7 @@ "score": 1 }, { - "label": "localite", + "category": "localite", "start": 5680, "end": 5689, "text": "Dunkerque", @@ -25,7 +25,7 @@ "score": 1 }, { - "label": "localite", + "category": "localite", "start": 88, "end": 98, "text": "Loon-Plage", @@ -33,7 +33,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 128, "end": 134, "text": "Xavier", @@ -41,7 +41,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 5312, "end": 5318, "text": "Xavier", @@ -49,7 +49,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 135, "end": 143, "text": "Bertrand", @@ -57,7 +57,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 5319, "end": 5327, "text": "Bertrand", @@ -65,7 +65,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 263, "end": 269, "text": "Franck", @@ -73,7 +73,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 270, "end": 276, "text": "Gonsse", @@ -82,7 +82,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 552, "end": 558, "text": "Gonsse", @@ -90,7 +90,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1090, "end": 1096, "text": "Gonsse", @@ -98,7 +98,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 5282, "end": 5288, "text": "Gonsse", @@ -106,7 +106,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 366, "end": 373, "text": "Nicolas", @@ -114,7 +114,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 374, "end": 381, "text": "Sarkozy", @@ -122,7 +122,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1407, "end": 1415, "text": "Philippe", @@ -130,7 +130,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 2247, "end": 2255, "text": "Philippe", @@ -138,7 +138,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 8135, "end": 8143, "text": "Philippe", @@ -146,7 +146,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1416, "end": 1422, "text": "Poutou", @@ -154,7 +154,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1494, "end": 1501, "text": "Olivier", @@ -162,7 +162,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 5844, "end": 5851, "text": "Olivier", @@ -170,7 +170,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1502, "end": 1512, "text": "Besancenot", @@ -178,7 +178,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1597, "end": 1604, "text": "Arlette", @@ -186,7 +186,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1605, "end": 1614, "text": "Laguiller", @@ -194,7 +194,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1831, "end": 1841, "text": "Christophe", @@ -202,7 +202,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1842, "end": 1851, "text": "Prudhomme", @@ -210,7 +210,7 @@ "score": 1 }, { - "label": "localite", + "category": "localite", "start": 1864, "end": 1881, "text": "Seine-Saint-Denis", @@ -218,7 +218,7 @@ "score": 1 }, { - "label": "localite", + "category": "localite", "start": 2317, "end": 2334, "text": "Seine-Saint-Denis", @@ -226,7 +226,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1883, "end": 1889, "text": "Pascal", @@ -234,7 +234,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1890, "end": 1899, "text": "Le Manach", @@ -242,7 +242,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1923, "end": 1927, "text": "Eric", @@ -250,7 +250,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1928, "end": 1936, "text": "Pecqueur", @@ -258,7 +258,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1981, "end": 1990, "text": "Sébastien", @@ -266,7 +266,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 1991, "end": 1998, "text": "Pointet", @@ -274,7 +274,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 2059, "end": 2065, "text": "Cécile", @@ -282,7 +282,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 2066, "end": 2073, "text": "Cukierman", @@ -290,7 +290,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 2256, "end": 2264, "text": "Martinez", @@ -298,7 +298,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 8739, "end": 8747, "text": "Martinez", @@ -306,7 +306,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 2853, "end": 2858, "text": "Cyril", @@ -314,7 +314,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 2859, "end": 2868, "text": "Chabanier", @@ -322,7 +322,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 3359, "end": 3363, "text": "Yvan", @@ -330,7 +330,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 3364, "end": 3373, "text": "Ricordeau", @@ -338,7 +338,7 @@ "score": 1 }, { - "label": "personneMorale", + "category": "personneMorale", "start": 288, "end": 292, "text": "CFDT", @@ -346,7 +346,7 @@ "score": 1 }, { - "label": "personneMorale", + "category": "personneMorale", "start": 3401, "end": 3405, "text": "CFDT", @@ -354,7 +354,7 @@ "score": 1 }, { - "label": "personneMorale", + "category": "personneMorale", "start": 5383, "end": 5387, "text": "CFDT", @@ -362,7 +362,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 3817, "end": 3825, "text": "Isabelle", @@ -370,7 +370,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 3826, "end": 3833, "text": "Chauvin", @@ -378,7 +378,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 4618, "end": 4626, "text": "François", @@ -386,7 +386,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 4627, "end": 4635, "text": "Hommeril", @@ -394,7 +394,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 5254, "end": 5262, "text": "Hommeril", @@ -402,7 +402,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 5977, "end": 5986, "text": "Jean-Yves", @@ -410,7 +410,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 5987, "end": 5995, "text": "Le Drian", @@ -418,7 +418,7 @@ "score": 1 }, { - "label": "personneMorale", + "category": "personneMorale", "start": 6055, "end": 6058, "text": "Gad", @@ -426,7 +426,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 6367, "end": 6371, "text": "Yves", @@ -434,7 +434,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 6372, "end": 6379, "text": "Veyrier", @@ -442,7 +442,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 7011, "end": 7019, "text": "Stéphane", @@ -450,7 +450,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 7020, "end": 7028, "text": "Blanchon", @@ -458,7 +458,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 7613, "end": 7621, "text": "Blanchon", @@ -466,7 +466,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 7883, "end": 7891, "text": "Blanchon", @@ -474,7 +474,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 9030, "end": 9038, "text": "Blanchon", @@ -482,7 +482,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 7350, "end": 7357, "text": "Doumont", @@ -490,7 +490,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 7631, "end": 7638, "text": "Doumont", @@ -498,7 +498,7 @@ "score": 1 }, { - "label": "localite", + "category": "localite", "start": 8880, "end": 8888, "text": "Nilvange", @@ -506,7 +506,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "professionnelMagistratGreffier", "start": 6796, "end": 6799, "text": "Yon", @@ -514,7 +514,7 @@ "score": 1 }, { - "label": "personnePhysique", + "category": "personnePhysique", "start": 9358, "end": 9361, "text": "Yon", @@ -522,6 +522,37 @@ "score": 1 } ], - "check_needed": false, - "checklist": [] + "checklist": [ + { + "check_type": "different_categories", + "message": "L'annotation 'Yon' est présente dans différentes catégories: ['Magistrat/Greffier', 'Personne physique']", + "entities": [ + { + "text": "Yon", + "start": 9358, + "category": "personnePhysique", + "source": "postprocess", + "score": 1.0, + "entityId": "personnePhysique_amaury", + "end": 9361 + }, + { + "text": "Yon", + "start": 6796, + "category": "professionnelMagistratGreffier", + "source": "postprocess", + "score": 1.0, + "entityId": "professionnelMagistratGreffier_amaury", + "end": 6799 + } + ], + "sentences": [ + { + "start": 0, + "end": 22 + } + ], + "metadata_text": [] + } + ] } diff --git a/packages/courDeCassation/storage-example/annotations/123457.json b/packages/courDeCassation/storage-example/annotations/123457.json index 5beea2e90..47095c70e 100644 --- a/packages/courDeCassation/storage-example/annotations/123457.json +++ b/packages/courDeCassation/storage-example/annotations/123457.json @@ -1,5 +1,4 @@ { "entities": [], - "check_needed": false, "checklist": [] } diff --git a/packages/courDeCassation/storage-example/documents/123452.json b/packages/courDeCassation/storage-example/documents/123452.json index 31e2adbbe..da001baf5 100644 --- a/packages/courDeCassation/storage-example/documents/123452.json +++ b/packages/courDeCassation/storage-example/documents/123452.json @@ -1,5 +1,14 @@ { + "chamberName": "", "dateDecision": "07/05/2021", + "jurisdictionName": "Mer", "originalText": "L’annonce a été faite sur le port de Dunkerque, au milieu des containers du terminal de Loon-Plage. Sur la photo, lundi 10 mai, Xavier Bertrand (ex-Les Républicains), en campagne pour sa réélection à la tête de la région des Hauts-de-France, et son nouvel allié, Franck Gonsse, le patron CFDT des dockers locaux. Une belle prise pour l’ancien ministre du travail de Nicolas Sarkozy : obtenir le renfort de personnalités provenant de la société civile offre la possibilité d’élargir sa base politique et de capter un potentiel gisement de voix.\nChez M. Gonsse, les motivations sont différentes : cumuler des casquettes de syndicaliste et d’élu territorial relève du « pragmatisme », explique-t-il. D’ailleurs, il est déjà conseiller délégué chargé des affaires maritimes et portuaires au conseil communautaire de Dunkerque. Une fonction qu’il a décrochée lors des municipales de 2020, en faisant acte de candidature parmi une coalition divers gauche. « Ça me permet d’avancer plus vite, affirme-t-il. Je n’ai plus besoin d’aller pleurer à la porte du politique, c’est du direct.Le cas de M. Gonsse est loin d’être isolé, mais il est difficile d’évaluer le nombre de syndicalistes qui briguent un mandat à l’occasion des élections des 20 et 27 juin. Si le phénomène n’est sans doute pas massif, il est récurrent : à chaque scrutin, des profils de ce type fleurissent. Même la présidentielle n’y échappe pas : Philippe Poutou, en lice pour le NPA en 2012 et 2017, était issu des rangs de la CGT ; Olivier Besancenot, le représentant de la LCR en 2002 et 2007, était encarté à SUD-PTT ; membre de FO, Arlette Laguiller a porté la bannière de Lutte ouvrière (LO) à six reprises…\nUne aura et un réseau\n\n»Pour les régionales de juin, la CGT fournit, à nouveau, plusieurs têtes de liste à des formations situées à la gauche de la gauche : Christophe Prudhomme pour LFI en Seine-Saint-Denis, Pascal Le Manach pour LO en Normandie ; Eric Pecqueur également pour LO dans les Hauts-de-France. Sébastien Pointet, de son côté, a rejoint l’attelage emmené par la communiste Cécile Cukierman en Auvergne-Rhône-Alpes pour en être le chef de file en Savoie. « Ces dernières années, on a même eu des militants qui ont concouru sous l’étiquette EELV ou PS », s’amuse Philippe Martinez, le secrétaire général de la centrale de Montreuil (Seine-Saint-Denis) : d’après lui, ceux qui, au sein de ses troupes, partent à l’assaut des urnes affichent des engagements politiques d’« une plus grande diversité » qu’il y a « trente ans ». Une allusion au fait que la CGT a, pendant des décennies, été perçue comme la « courroie de transmission » du PCF. A tel point que l’appareil de la confédération fut longtemps dominé par des figures du parti de la place du Colonel-Fabien.\nLe fait que des syndicalistes soient enrôlés par des formations politiques n’a rien d’étonnant, décrypte Cyril Chabanier, le président de la CFTC : « Ils ont des compétences recherchées et certains jouissent d’une aura certaine dans leur territoire, tout en s’appuyant sur un réseau qui peut être dense », souligne-t-il. Sans compter qu’il devient « de plus en plus compliqué de trouver des gens pour constituer des listes », complète-t-il. « Que certains militants syndicaux fassent le choix de se présenter à une élection, c’est logique, sinon le corps politique serait assez restreint », note, pour sa part, Yvan Ricordeau, secrétaire national de la CFDT.\nEnfin, l’activité d’élu est vue comme « le prolongement de l’engagement syndical », selon la formule de Patricia Téjas, une des responsables du comité régional de la CGT en Provence-Alpes-Côte d’Azur en lice pour les départementales sous les couleurs du PCF dans le Vaucluse. « Il s’agit d’entrer dans les institutions pour faire bouger les lignes sur le terrain et améliorer le sort des citoyens », renchérit Isabelle Chauvin, candidate LFI aux départementales dans les Bouches-du-Rhône et élue CGT du personnel dans un groupe d’enseignement privé.\nChaque confédération a ses règles\n\nMais de telles démarches soulèvent parfois des débats en interne car elles peuvent être vues comme une atteinte au principe d’indépendance du syndicalisme vis-à-vis des partis. Un principe inscrit dans la Charte d’Amiens, adoptée en 1906 par la CGT. Bien qu’il ait été transgressé maintes fois – à commencer par la CGT elle-même – , il demeure une boussole pour toutes les centrales de salariés, conduisant celles-ci à édicter des règles. « La contrainte que l’on impose à nos militants qui participent à une élection est de ne pas faire état de leur appartenance à notre organisation ni de leurs mandats syndicaux », indique François Hommeril, le président de la CFE-CGC. Dans certaines confédérations, il peut être demandé à ceux qui ont des responsabilités de les mettre entre parenthèses, le temps de la campagne, et de démissionner s’il y a un conflit d’intérêts en cas d’élection. A la CGT et à FO, les membres du bureau confédéral n’ont pas le droit de se présenter. L’UNSA, elle, semble être un peu plus permissive pour ses hauts dirigeants : une secrétaire nationale, Emilie Trigo, figure sur la liste socialiste d’Audrey Pulvar en Ile-de-France.\nCeux qui sortent des clous « peuvent faire l’objet de sanctions allant jusqu’à l’exclusion », enchaîne M. Hommeril. Dans le cas de M. Gonsse, le cédétiste rallié à Xavier Bertrand dans les Hauts-de-France, la direction nationale de la CFDT tient à ce que les choses soient claires : « S’il devient conseiller régional, il restituera son mandat syndical. Cela a été discuté avec lui en amont. » Mais l’intéressé ne l’entend pas de cette oreille : « en aucun cas » il ne renoncera à son rôle auprès des salariés des ports et docks de Dunkerque.\nA l’inverse, certains syndicalistes qui tentent l’aventure politique abandonnent d’eux-mêmes leurs fonctions et rendent même leur carte. Une décision qu’Olivier Le Bras a prise en 2015, quand il s’est lancé, avec succès, dans la course aux régionales en Bretagne, au côté du socialiste Jean-Yves Le Drian. Cet ancien représentant du personnel FO dans l’entreprise Gad confie qu’il ne voulait « pas exposer » sa confédération. En outre, poursuit-il, « c’est compliqué » d’être à la fois élu territorial et défenseur des intérêts des salariés.\n« Chacun a le droit à ses engagements politique, philosophique, religieux, sous réserve qu’ils ne les introduisent pas à FO », résume Yves Veyrier, le numéro un de la confédération. Une gageure quand on connaît le poids des courants politiques, notamment les trotskistes et les anarchistes, dans les diverses composantes de sa centrale.\n« Dans les années 1980, ça allait encore de soi de dire que les syndicats et la gauche, c’est la même chose, mais, au fil du temps, un fossé symbolique s’est creusé entre les champs syndical et partisan », contrebalance Karel Yon, sociologue et chargé de recherche au CNRS. Dans le même temps, les idées d’extrême droite se sont peu à peu propagées parmi les salariés et chez ceux qui les représentent.\n« Chasse aux sorcières »\n\nIl y a peu, Stéphane Blanchon était à la tête de la fédération UNSA santé et action sociale. Désormais tête de liste Rassemblement national (RN) dans la Drôme pour les régionales en Auvergne-Rhône-Alpes, il explique avoir sauté le pas, car il souhaitait passer « à une autre sorte d’action ». Sa candidature ainsi que celle d’un de ses collègues, Luc Doumont, aux départementales, ont provoqué l’ire du secrétaire général de l’UNSA : « On ne transige pas avec les valeurs, s’exclame Laurent Escure. Le RN est le seul parti qui a un problème avec un syndicalisme libre et démocratique. »\nPour lui, l’attitude de M. Blanchon et de M. Doumont est le résultat d’« un mélange de démarche individuelle camouflée et d’opportunisme ». « Aujourd’hui, le RN, c’est bankable pour se faire élire, dénonce-t-il. Ce que l’on ne mesure pas, c’est la profondeur de l’offensive. » Une réaction que M. Blanchon ne comprend pas : il critique une direction devenue « parano » et s’estime victime d’« une chasse aux sorcières ». Car la bascule vers le RN équivaut, bien souvent, à une exclusion. Sauf si les transfuges lâchent leurs fonctions, à l’image de Philippe Théveniaud : cet ancien président de la CFTC dans la Somme a abandonné tous ses mandats peu avant de révéler sa candidature aux régionales dans les Hauts-de-France sous l’étiquette RN.\nQuant à ceux qui ne veulent pas partir, les centrales s’appuient sur leur charte des valeurs pour les bannir. Mais les numéros un syndicaux ont rarement la main – la procédure relevant la plupart du temps des fédérations professionnelles ou des structures locales. « Ce n’est pas moi qui commande, je ne peux que donner mon avis, mais généralement, dans ces cas-là, ça se règle assez facilement », certifie M. Martinez.\nEn 2011, la fédération CGT des services publics avait cependant dû désaffilier le syndicat des agents territoriaux de la mairie de Nilvange (Moselle), qui soutenait son responsable, Fabien Engelmann, alors candidat aux cantonales sous l’étiquette Front national. Dans le cas de M. Blanchon, sa fédération lui a retiré son mandat, mais, « techniquement », il n’est toujours pas évincé de son syndicat, prétend-il : « Je ne vois pas ce que j’ai fait de si monstrueux. La charte des valeurs de l’UNSA, je n’en renie pas une ligne. »\nPour l’extrême droite, ces candidatures constituent une bonne opération. Si M. Yon rappelle l’« ambivalence » du parti de Marine Le Pen vis-à-vis des syndicats, il relève aussi que « le RN a tout intérêt à valoriser le fait qu’un syndicaliste de telle ou telle organisation les rejoint. Cela s’inscrit dans leur stratégie de dédiabolisation, qui veut que le RN soit le premier parti ouvrier – quand on ne compte pas les abstentionnistes ».", - "sourceId": 123452 + "registerNumber": 123452, + "sourceId": 123452, + "sourceName": "jurinet", + "appeals": [], + "occultation": { + "motivationOccultation": null, + "additionalTerms": "Occulter le terme Dunkerque" + } } diff --git a/packages/courDeCassation/storage-example/documents/123456.json b/packages/courDeCassation/storage-example/documents/123456.json index 53f012aa2..0c0b7672a 100644 --- a/packages/courDeCassation/storage-example/documents/123456.json +++ b/packages/courDeCassation/storage-example/documents/123456.json @@ -5,5 +5,9 @@ "originalText": "L’annonce a été faite sur le port de Dunkerque, au milieu des containers du terminal de Loon-Plage. Sur la photo, lundi 10 mai, Xavier Bertrand (ex-Les Républicains), en campagne pour sa réélection à la tête de la région des Hauts-de-France, et son nouvel allié, Franck Gonsse, le patron CFDT des dockers locaux. Une belle prise pour l’ancien ministre du travail de Nicolas Sarkozy : obtenir le renfort de personnalités provenant de la société civile offre la possibilité d’élargir sa base politique et de capter un potentiel gisement de voix.\nChez M. Gonsse, les motivations sont différentes : cumuler des casquettes de syndicaliste et d’élu territorial relève du « pragmatisme », explique-t-il. D’ailleurs, il est déjà conseiller délégué chargé des affaires maritimes et portuaires au conseil communautaire de Dunkerque. Une fonction qu’il a décrochée lors des municipales de 2020, en faisant acte de candidature parmi une coalition divers gauche. « Ça me permet d’avancer plus vite, affirme-t-il. Je n’ai plus besoin d’aller pleurer à la porte du politique, c’est du direct.Le cas de M. Gonsse est loin d’être isolé, mais il est difficile d’évaluer le nombre de syndicalistes qui briguent un mandat à l’occasion des élections des 20 et 27 juin. Si le phénomène n’est sans doute pas massif, il est récurrent : à chaque scrutin, des profils de ce type fleurissent. Même la présidentielle n’y échappe pas : Philippe Poutou, en lice pour le NPA en 2012 et 2017, était issu des rangs de la CGT ; Olivier Besancenot, le représentant de la LCR en 2002 et 2007, était encarté à SUD-PTT ; membre de FO, Arlette Laguiller a porté la bannière de Lutte ouvrière (LO) à six reprises…\nUne aura et un réseau\n\n»Pour les régionales de juin, la CGT fournit, à nouveau, plusieurs têtes de liste à des formations situées à la gauche de la gauche : Christophe Prudhomme pour LFI en Seine-Saint-Denis, Pascal Le Manach pour LO en Normandie ; Eric Pecqueur également pour LO dans les Hauts-de-France. Sébastien Pointet, de son côté, a rejoint l’attelage emmené par la communiste Cécile Cukierman en Auvergne-Rhône-Alpes pour en être le chef de file en Savoie. « Ces dernières années, on a même eu des militants qui ont concouru sous l’étiquette EELV ou PS », s’amuse Philippe Martinez, le secrétaire général de la centrale de Montreuil (Seine-Saint-Denis) : d’après lui, ceux qui, au sein de ses troupes, partent à l’assaut des urnes affichent des engagements politiques d’« une plus grande diversité » qu’il y a « trente ans ». Une allusion au fait que la CGT a, pendant des décennies, été perçue comme la « courroie de transmission » du PCF. A tel point que l’appareil de la confédération fut longtemps dominé par des figures du parti de la place du Colonel-Fabien.\nLe fait que des syndicalistes soient enrôlés par des formations politiques n’a rien d’étonnant, décrypte Cyril Chabanier, le président de la CFTC : « Ils ont des compétences recherchées et certains jouissent d’une aura certaine dans leur territoire, tout en s’appuyant sur un réseau qui peut être dense », souligne-t-il. Sans compter qu’il devient « de plus en plus compliqué de trouver des gens pour constituer des listes », complète-t-il. « Que certains militants syndicaux fassent le choix de se présenter à une élection, c’est logique, sinon le corps politique serait assez restreint », note, pour sa part, Yvan Ricordeau, secrétaire national de la CFDT.\nEnfin, l’activité d’élu est vue comme « le prolongement de l’engagement syndical », selon la formule de Patricia Téjas, une des responsables du comité régional de la CGT en Provence-Alpes-Côte d’Azur en lice pour les départementales sous les couleurs du PCF dans le Vaucluse. « Il s’agit d’entrer dans les institutions pour faire bouger les lignes sur le terrain et améliorer le sort des citoyens », renchérit Isabelle Chauvin, candidate LFI aux départementales dans les Bouches-du-Rhône et élue CGT du personnel dans un groupe d’enseignement privé.\nChaque confédération a ses règles\n\nMais de telles démarches soulèvent parfois des débats en interne car elles peuvent être vues comme une atteinte au principe d’indépendance du syndicalisme vis-à-vis des partis. Un principe inscrit dans la Charte d’Amiens, adoptée en 1906 par la CGT. Bien qu’il ait été transgressé maintes fois – à commencer par la CGT elle-même – , il demeure une boussole pour toutes les centrales de salariés, conduisant celles-ci à édicter des règles. « La contrainte que l’on impose à nos militants qui participent à une élection est de ne pas faire état de leur appartenance à notre organisation ni de leurs mandats syndicaux », indique François Hommeril, le président de la CFE-CGC. Dans certaines confédérations, il peut être demandé à ceux qui ont des responsabilités de les mettre entre parenthèses, le temps de la campagne, et de démissionner s’il y a un conflit d’intérêts en cas d’élection. A la CGT et à FO, les membres du bureau confédéral n’ont pas le droit de se présenter. L’UNSA, elle, semble être un peu plus permissive pour ses hauts dirigeants : une secrétaire nationale, Emilie Trigo, figure sur la liste socialiste d’Audrey Pulvar en Ile-de-France.\nCeux qui sortent des clous « peuvent faire l’objet de sanctions allant jusqu’à l’exclusion », enchaîne M. Hommeril. Dans le cas de M. Gonsse, le cédétiste rallié à Xavier Bertrand dans les Hauts-de-France, la direction nationale de la CFDT tient à ce que les choses soient claires : « S’il devient conseiller régional, il restituera son mandat syndical. Cela a été discuté avec lui en amont. » Mais l’intéressé ne l’entend pas de cette oreille : « en aucun cas » il ne renoncera à son rôle auprès des salariés des ports et docks de Dunkerque.\nA l’inverse, certains syndicalistes qui tentent l’aventure politique abandonnent d’eux-mêmes leurs fonctions et rendent même leur carte. Une décision qu’Olivier Le Bras a prise en 2015, quand il s’est lancé, avec succès, dans la course aux régionales en Bretagne, au côté du socialiste Jean-Yves Le Drian. Cet ancien représentant du personnel FO dans l’entreprise Gad confie qu’il ne voulait « pas exposer » sa confédération. En outre, poursuit-il, « c’est compliqué » d’être à la fois élu territorial et défenseur des intérêts des salariés.\n« Chacun a le droit à ses engagements politique, philosophique, religieux, sous réserve qu’ils ne les introduisent pas à FO », résume Yves Veyrier, le numéro un de la confédération. Une gageure quand on connaît le poids des courants politiques, notamment les trotskistes et les anarchistes, dans les diverses composantes de sa centrale.\n« Dans les années 1980, ça allait encore de soi de dire que les syndicats et la gauche, c’est la même chose, mais, au fil du temps, un fossé symbolique s’est creusé entre les champs syndical et partisan », contrebalance Karel Yon, sociologue et chargé de recherche au CNRS. Dans le même temps, les idées d’extrême droite se sont peu à peu propagées parmi les salariés et chez ceux qui les représentent.\n« Chasse aux sorcières »\n\nIl y a peu, Stéphane Blanchon était à la tête de la fédération UNSA santé et action sociale. Désormais tête de liste Rassemblement national (RN) dans la Drôme pour les régionales en Auvergne-Rhône-Alpes, il explique avoir sauté le pas, car il souhaitait passer « à une autre sorte d’action ». Sa candidature ainsi que celle d’un de ses collègues, Luc Doumont, aux départementales, ont provoqué l’ire du secrétaire général de l’UNSA : « On ne transige pas avec les valeurs, s’exclame Laurent Escure. Le RN est le seul parti qui a un problème avec un syndicalisme libre et démocratique. »\nPour lui, l’attitude de M. Blanchon et de M. Doumont est le résultat d’« un mélange de démarche individuelle camouflée et d’opportunisme ». « Aujourd’hui, le RN, c’est bankable pour se faire élire, dénonce-t-il. Ce que l’on ne mesure pas, c’est la profondeur de l’offensive. » Une réaction que M. Blanchon ne comprend pas : il critique une direction devenue « parano » et s’estime victime d’« une chasse aux sorcières ». Car la bascule vers le RN équivaut, bien souvent, à une exclusion. Sauf si les transfuges lâchent leurs fonctions, à l’image de Philippe Théveniaud : cet ancien président de la CFTC dans la Somme a abandonné tous ses mandats peu avant de révéler sa candidature aux régionales dans les Hauts-de-France sous l’étiquette RN.\nQuant à ceux qui ne veulent pas partir, les centrales s’appuient sur leur charte des valeurs pour les bannir. Mais les numéros un syndicaux ont rarement la main – la procédure relevant la plupart du temps des fédérations professionnelles ou des structures locales. « Ce n’est pas moi qui commande, je ne peux que donner mon avis, mais généralement, dans ces cas-là, ça se règle assez facilement », certifie M. Martinez.\nEn 2011, la fédération CGT des services publics avait cependant dû désaffilier le syndicat des agents territoriaux de la mairie de Nilvange (Moselle), qui soutenait son responsable, Fabien Engelmann, alors candidat aux cantonales sous l’étiquette Front national. Dans le cas de M. Blanchon, sa fédération lui a retiré son mandat, mais, « techniquement », il n’est toujours pas évincé de son syndicat, prétend-il : « Je ne vois pas ce que j’ai fait de si monstrueux. La charte des valeurs de l’UNSA, je n’en renie pas une ligne. »\nPour l’extrême droite, ces candidatures constituent une bonne opération. Si M. Yon rappelle l’« ambivalence » du parti de Marine Le Pen vis-à-vis des syndicats, il relève aussi que « le RN a tout intérêt à valoriser le fait qu’un syndicaliste de telle ou telle organisation les rejoint. Cela s’inscrit dans leur stratégie de dédiabolisation, qui veut que le RN soit le premier parti ouvrier – quand on ne compte pas les abstentionnistes ».", "registerNumber": 123456, "sourceId": 123456, - "sourceName": "Le Monde" + "sourceName": "juritj", + "appeals": [], + "occultation": { + "motivationOccultation": null + } } diff --git a/packages/courDeCassation/storage-example/documents/123457.json b/packages/courDeCassation/storage-example/documents/123457.json index 934f5976e..5ff9cf284 100644 --- a/packages/courDeCassation/storage-example/documents/123457.json +++ b/packages/courDeCassation/storage-example/documents/123457.json @@ -1,5 +1,13 @@ { - "dateDecision": "16/06/2021", + "chamberName": "Gryffondor", + "dateDecision": "10/06/2021", + "jurisdictionName": "The Daily Prophet", "originalText": "Harry Potter à l'école des sorciers (Harry Potter and the Philosopher's Stone) est le premier roman de la série littéraire centrée sur le personnage de Harry Potter, créé par J. K. Rowling. Sorti le 26 juin 1997, il est initialement tiré à 500 exemplaires. En France, le roman a été publié le 9 octobre 1998.\nIl trouve son importance puisqu'il sert de base introductive aux six autres tomes de la série ainsi qu'à la pièce de théâtre Harry Potter et l'Enfant maudit. Il permet à l'auteur de mettre en place l'univers, de familiariser le lecteur avec ses personnages, les lieux, les objets magiques, les mœurs et tout le vocabulaire propre à son monde. Ce premier tome installe progressivement les nœuds de l'intrigue grâce à des indices dissimulés pour ne pas éveiller les soupçons du lecteur dès le début de l'histoire.\nLe livre, qui est le premier roman de Rowling à être publié, est écrit entre approximativement juin 1990 et 1995. En 1990, l'auteur prend un train de Manchester à Londres après une recherche d'appartements, et l'idée de Harry Potter lui vient soudainement. Elle imagine alors un garçon de petite taille, aux cheveux noirs et aux lunettes, apprenant qu'il est un sorcier et bien plus encore. Elle commence a écrire L'école des sorciers le soir même et rédigera l'intégralité du manuscrit sur une machine à écrire. La mère de Rowling meurt à l'âge de quarante-cinq ans, alors que l'auteur rédige le livre depuis environ six mois. Pour faire face à cette douleur, Rowling transfère sa propre angoisse à son héros, bien qu'il était prévu dès le début de l'écriture que Harry serait un orphelin. Rowling séjourne au Portugal pour prendre de la distance et y rédige son chapitre préféré de ce premier roman : « Le Miroir du Riséd ». Elle passe environ sept ans à mettre en place son univers sorcier, en commençant par établir les lois et les « limites » de la magie.\nAprès que son livre a été accepté par Bloomsbury, elle obtient une subvention de 4 000 livres du Scottish Arts Council, ce qui lui permet de planifier les suites. Bloomsbury passe une année à essayer de vendre le livre à un éditeur, dont la plupart estime que le livre était trop long pour des enfants.\n\nRésumé de l'intrigue\nAprès la mort de ses parents (Lily et James Potter), Harry Potter est recueilli par sa tante maternelle Pétunia et son oncle Vernon à l'âge d'un an. Ces derniers, animés depuis toujours d'une haine féroce envers les parents du garçon qu'ils qualifient de gens « bizarres », voire de « monstres , traitent froidement leur neveu et demeurent indifférents aux humiliations que leur fils Dudley lui fait subir. Harry ignore tout de l'histoire de ses parents, si ce n'est qu'ils ont été, semble-t-il, tués dans un accident de voiture. Cependant, le jour des onze ans de Harry, un demi-géant du nom de Rubeus Hagrid vient le chercher pour l'informer de son inscription à Poudlard, une école de sorcellerie où il est inscrit depuis sa naissance, et lui remettre sa lettre. Il lui révèle qu’il a toujours été un sorcier, tout comme l'étaient ses parents, tués en réalité par le plus puissant mage noir du monde de la sorcellerie : Voldemort (surnommé « Celui-Dont-On-Ne-Doit-Pas-Prononcer-Le-Nom », « Vous savez qui » ou « Tu sais qui »). Ce serait Harry lui-même, alors qu'il n'était encore qu'un bébé, qui aurait fait ricocher le sortilège que Voldemort lui destinait, neutralisant ses pouvoirs et le réduisant à l'état de créature insignifiante. Le fait d'avoir vécu son enfance chez son oncle et sa tante dépourvus de pouvoirs magiques lui aurait permis de grandir à l'abri de l'admiration qu'il suscite dans le monde des sorciers. Hagrid l'accompagne ensuite sur le chemin de Traverse pour acheter sa baguette magique et ses fournitures scolaires. Le demi-géant en profite pour récupérer sur ordre de Dumbledore un mystérieux paquet à Gringotts, la banque des sorciers.\nHarry fait la connaissance de Ron Weasley et Hermione Granger dans le Poudlard Express, le train les conduisant à l'école, et découvre rapidement l'hostilité que semblent lui vouer le jeune Drago Malefoy et le professeur de potions Severus Rogue. À leur arrivée à Poudlard, les élèves sont répartis dans différentes maisons après avoir enfilé le « choixpeau » qui analyse leur personnalité : Harry, Ron et Hermione sont tous les trois répartis dans la maison Gryffondor.\nUn peu plus tard dans l'année, les trois amis découvrent par hasard qu'un immense chien à trois têtes est hébergé au sein même du château et semble garder quelque chose sous une trappe, sans doute l'objet mystérieux que Hagrid a récupéré à la banque des sorciers juste avant la rentrée. Harry est persuadé que le professeur Rogue tente de faire diversion pour essayer de passer devant le chien à trois têtes et récupérer l'objet en question, qui semble concerner Dumbledore et l'un de ses amis, un certain Nicolas Flamel\nLe jour de Noël, Harry découvre parmi ses cadeaux une cape d'invisibilité ayant appartenu à son père James Potter. Il décide de s'en servir, et pour éviter Rogue et Rusard qui se trouvent sur son chemin dans les couloirs, se cache dans une salle de classe désaffectée où il découvre un miroir étrange, le miroir du Riséd, ayant le pouvoir de montrer le désir le plus cher de la personne qui observe son reflet26. Harry observe ainsi avec fascination ses parents disparus avec lesquels il peut interagir.\nHarry, Ron et Hermione comprennent que l'objet si précieux caché sous la trappe est une pierre philosophale créée par Nicolas Flamel, et qui aurait le pouvoir d'offrir l'immortalité. Ils sont persuadés que Voldemort, par le biais du professeur Rogue, cherche à s'en emparer. Pour récupérer la pierre en premier, Harry, Ron et Hermione passent sous la trappe gardée par le chien et franchissent une série d'obstacles conçus par les plus talentueux professeurs de Poudlard. Dans la pièce de la dernière énigme, ce n'est pas le professeur Rogue que retrouve Harry mais le professeur Quirrell, professeur de défense contre les forces du Mal, qui se tient devant le miroir du Riséd, placé là par Dumbledore.\nHarry est terrifié lorsque Quirrell déroule le turban violet qu'il porte sur la tête : le couvre-chef dissimule en réalité le visage de Voldemort, formé à l'arrière du crâne de Quirrell ; le mage noir avait « emprunté » le corps du professeur pour se rapprocher de la pierre cachée et pour lui transmettre plus aisément ses ordres. Harry parvient à récupérer la pierre grâce au miroir. Voldemort ordonne alors à Quirrell de tuer Harry mais Dumbledore s'interpose in extremis.", - "sourceId": 123457 + "registerNumber": 123457, + "sourceId": 123457, + "sourceName": "jurinet", + "appeals": [], + "occultation": { + "motivationOccultation": null + } } diff --git a/packages/generic/backend/src/lib/annotator/buildAnnotator.ts b/packages/generic/backend/src/lib/annotator/buildAnnotator.ts index 77ab37546..e92b386fe 100644 --- a/packages/generic/backend/src/lib/annotator/buildAnnotator.ts +++ b/packages/generic/backend/src/lib/annotator/buildAnnotator.ts @@ -272,18 +272,19 @@ function buildAnnotator( } } - //Todo : create report only if report is not null - await createReport(report); - logger.log({ - operationName: 'annotateDocument', - msg: 'Annotation report created in DB', - data: { - decision: { - sourceId: document.documentNumber, - sourceName: document.source, + if (report.checklist.length > 0) { + await createReport(report); + logger.log({ + operationName: 'annotateDocument', + msg: 'Annotation report created in DB', + data: { + decision: { + sourceId: document.documentNumber, + sourceName: document.source, + }, }, - }, - }); + }); + } if ( additionalTermsParsingFailed !== null && diff --git a/packages/generic/client/package.json b/packages/generic/client/package.json index f5286cfd8..5ce5227b4 100644 --- a/packages/generic/client/package.json +++ b/packages/generic/client/package.json @@ -63,7 +63,7 @@ "axios": "^0.24.0", "dateformat": "^5.0.3", "http2": "^3.3.7", - "pelta-design-system": "https://github.com/Cour-de-cassation/pelta-design-system#e508f818ac443ad048e5cd1b74f42ec7f0bd046d", + "pelta-design-system": "https://github.com/Cour-de-cassation/pelta-design-system#b9d52684da753a250ba351588c31da7bc7280cbe", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router-dom": "^5.2.0", diff --git a/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/AnnotationsPanel.tsx b/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/AnnotationsPanel.tsx index f5fd131a5..e77425878 100644 --- a/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/AnnotationsPanel.tsx +++ b/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/AnnotationsPanel.tsx @@ -7,6 +7,7 @@ import { annotationPerCategoryAndEntityType, splittedTextByLineType } from '../l import { CategoryTable } from './CategoryTable'; import { EmptyCategory } from './EmptyCategory'; import { StrickenCategory } from './StrickenCategory'; +import { Checklist } from './Checklist'; export { AnnotationsPanel }; @@ -154,20 +155,8 @@ function AnnotationsPanel(props: { } } - function renderChecklist(checklist: string[]) { - return ( -
-
- -
-
- {wordings.homePage.checklist} - {checklist.map((checklistElement) => ( - - {checklistElement} - ))} -
-
- ); + function renderChecklist(checklist: annotationReportType['checklist']) { + return ; } function renderPartiallyPublicWarning() { @@ -247,22 +236,6 @@ function AnnotationsPanel(props: { flex: 1, flexDirection: 'column', }, - checklistContainer: { - padding: theme.spacing * 2, - marginBottom: theme.spacing, - display: 'flex', - flex: 1, - justifyContent: 'space-between', - borderRadius: theme.shape.borderRadius.l, - }, - checklistLeftContainer: { - marginRight: theme.spacing * 3, - }, - checklistRightContainer: { - display: 'flex', - flex: 1, - flexDirection: 'column', - }, categoryContainer: { marginBottom: theme.spacing, }, diff --git a/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/Checklist.tsx b/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/Checklist.tsx new file mode 100644 index 000000000..b5cfb47d7 --- /dev/null +++ b/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/Checklist.tsx @@ -0,0 +1,77 @@ +import React, { useState } from 'react'; +import { annotationReportType } from '@label/core'; +import { Accordion, customThemeType, Icon, Text, useCustomTheme } from 'pelta-design-system'; +import { wordings } from '../../../../wordings'; +import { ChecklistEntry } from './ChecklistEntry'; +import { splittedTextByLineType } from '../lib'; + +export { Checklist }; + +const ACCORDION_HEADER_PADDING = 8; + +function Checklist(props: { + checklist: annotationReportType['checklist']; + splittedTextByLine: splittedTextByLineType; +}) { + const theme = useCustomTheme(); + const iconSize = theme.shape.borderRadius.l; + const styles = buildStyles(theme); + + const [isExpanded, setIsExpanded] = useState(true); + + return ( + +
+ +
+ {`${wordings.homePage.checklist}`} +
+
+
+ +
+ + } + body={ +
+ {props.checklist.map((item, index) => ( +
+ +
+ ))} +
+ } + onChange={setIsExpanded} + defaultExpanded={true} + /> + ); + + function buildStyles(theme: customThemeType) { + return { + accordionHeaderContainer: { + padding: ACCORDION_HEADER_PADDING, + minHeight: iconSize, + }, + accordionHeader: { + display: 'flex', + width: '100%', + justifyContent: 'space-between', + alignItems: 'center', + }, + accordionHeaderLeftContainer: { + display: 'flex', + alignItems: 'center', + }, + categoryContainer: { + paddingLeft: theme.spacing, + }, + accordionHeaderArrowContainer: { + paddingRight: theme.spacing, + paddingTop: '4px', + }, + } as const; + } +} diff --git a/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/ChecklistEntry.tsx b/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/ChecklistEntry.tsx new file mode 100644 index 000000000..d710f2bf4 --- /dev/null +++ b/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/ChecklistEntry.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import styled from 'styled-components'; +import { customThemeType, Text, useCustomTheme } from 'pelta-design-system'; +import { useChecklistEntryHandler } from './useChecklistEntryHandler'; +import { useViewerScrollerHandler } from '../../../../services/viewerScroller'; +import { annotationReportType } from '@label/core'; +import { splittedTextByLineType } from '../lib'; + +export { ChecklistEntry }; + +function ChecklistEntry(props: { + check: annotationReportType['checklist'][number]; + splittedTextByLine: splittedTextByLineType; +}) { + const { check, splittedTextByLine } = props; + const theme = useCustomTheme(); + const viewerScrollerHandler = useViewerScrollerHandler(); + const checklistEntryHandler = useChecklistEntryHandler({ + onLeaveAnnotationMode, + onResetViewerMode, + splittedTextByLine, + }); + + const selectChecklist = () => { + const isChecklistSelected = checklistEntryHandler.isSelected(check.message); + if (isChecklistSelected) { + checklistEntryHandler.setSelected(undefined); + } else { + checklistEntryHandler.setSelected(check); + } + }; + + function onLeaveAnnotationMode() { + viewerScrollerHandler.storeCurrentVerticalPosition(); + } + + function onResetViewerMode() { + viewerScrollerHandler.scrollToStoredVerticalPosition(); + } + + return ( + + {`- ${check.message}`} + + ); +} + +const Div_ChecklistEntry = styled.div<{ isSelected: boolean }>` + ${({ theme, isSelected }: { theme: customThemeType; isSelected: boolean }) => ` + padding: ${theme.spacing}px ${theme.spacing * 2}px; + background-color: ${isSelected ? theme.colors.default.hoveredTextColor : theme.colors.default.background}; + cursor: pointer; + border-radius: ${theme.shape.borderRadius.m}px; + + &:hover { + background: linear-gradient(to left, ${theme.colors.default.hoveredBackground}, 50%, transparent); + } + `} +`; diff --git a/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/useChecklistEntryHandler.ts b/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/useChecklistEntryHandler.ts new file mode 100644 index 000000000..fd0429ed8 --- /dev/null +++ b/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/useChecklistEntryHandler.ts @@ -0,0 +1,93 @@ +import { annotationReportType } from '@label/core'; +import { useDocumentViewerModeHandler } from '../../../../services/documentViewerMode'; +import { useState } from 'react'; +import { splittedTextByLineType } from '../lib'; + +export { useChecklistEntryHandler }; + +function useChecklistEntryHandler({ + splittedTextByLine, + onLeaveAnnotationMode, + onResetViewerMode, +}: { + splittedTextByLine: splittedTextByLineType; + onLeaveAnnotationMode: () => void; + onResetViewerMode: () => void; +}) { + const [checklistFocused, setChecklistFocused] = useState(undefined); + const documentViewerModeHandler = useDocumentViewerModeHandler(); + + return { + isFocused: (message: string) => checklistFocused === message, + isSelected, + setFocus: (message?: string) => setChecklistFocused(message), + setSelected, + }; + + function setSelected(check?: annotationReportType['checklist'][number]) { + if (check) { + if (documentViewerModeHandler.documentViewerMode.kind != 'checklist') { + onLeaveAnnotationMode(); + } + const checklistLines = filterLinesByCheck(check, splittedTextByLine).map(({ line }) => line); + documentViewerModeHandler.setChecklistMode(check, checklistLines); + } else { + onResetViewerMode(); + documentViewerModeHandler.resetViewerMode(); + } + } + + function isSelected(message: string) { + const selectedChecklistMessage = + documentViewerModeHandler.documentViewerMode.kind === 'checklist' + ? documentViewerModeHandler.documentViewerMode.check.message + : undefined; + + return selectedChecklistMessage === message; + } + + function filterLinesByCheck( + check: annotationReportType['checklist'][number], + splittedTextByLine: splittedTextByLineType, + ): splittedTextByLineType { + const result: splittedTextByLineType = []; + + check.entities.forEach((entity) => { + const linesWithEntityId = splittedTextByLine.filter(({ content }) => + content.some((chunk) => { + if (chunk.type === 'annotation') { + return chunk.annotation.entityId === entity.entityId; + } + }), + ); + + if (linesWithEntityId.length > 0) { + result.push(...linesWithEntityId); + } else { + const linesWithIndex = splittedTextByLine.filter(({ content }) => + content.some((chunk) => { + if (chunk.type === 'text') { + return chunk.content.index >= entity.start && chunk.content.index <= entity.end; + } + }), + ); + result.push(...linesWithIndex); + } + }); + + if (result.length === 0 && check.sentences) { + check.sentences.forEach((sentence) => { + const linesWithSentence = splittedTextByLine.filter(({ content }) => + content.some((chunk) => { + if (chunk.type === 'text') { + return chunk.content.index >= sentence.start && chunk.content.index <= sentence.end; + } + }), + ); + result.push(...linesWithSentence); + }); + } + + return result; + } +} diff --git a/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/useEntityEntryHandler.ts b/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/useEntityEntryHandler.ts index f7da36de9..aa959113b 100644 --- a/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/useEntityEntryHandler.ts +++ b/packages/generic/client/src/pages/Home/DocumentAnnotator/AnnotationsPanel/useEntityEntryHandler.ts @@ -29,7 +29,7 @@ function useEntityEntryHandler({ function setSelected(category?: string, entityId?: string) { if (category && entityId) { - if (documentViewerModeHandler.documentViewerMode.kind === 'annotation') { + if (documentViewerModeHandler.documentViewerMode.kind != 'occurrence') { onLeaveAnnotationMode(); } const entityLines = filterLineByEntityId(entityId, splittedTextByLine).map(({ line }) => line); diff --git a/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentPanelHeader/ChecklistHeader.tsx b/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentPanelHeader/ChecklistHeader.tsx new file mode 100644 index 000000000..b21853818 --- /dev/null +++ b/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentPanelHeader/ChecklistHeader.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { useCustomTheme, Header, IconButton, Text, Icon, customThemeType } from 'pelta-design-system'; +import { heights } from '../../../../../styles'; +import { wordings } from '../../../../../wordings'; +import { useDocumentViewerModeHandler } from '../../../../../services/documentViewerMode'; + +export { ChecklistHeader }; + +const HELP_ICON_SIZE = 40; + +function ChecklistHeader(props: { message: string }) { + const { resetViewerMode } = useDocumentViewerModeHandler(); + const theme = useCustomTheme(); + const styles = buildStyles(theme); + + return ( +
+
+ +
+
+ + {props.message} + +
+ , + ]} + rightHeaderComponents={[]} + spaceBetweenComponents={0} + variant="mainLeft" + style={styles.header} + /> + ); +} + +function buildStyles(theme: customThemeType) { + return { + header: { + paddingRight: theme.spacing * 2, + height: heights.annotatorPanelHeader, + }, + leftHeader: { + display: 'flex', + }, + checkIconContainer: { + width: HELP_ICON_SIZE, + }, + checkInfosContainer: { + display: 'flex', + flexDirection: 'column', + paddingLeft: theme.spacing, + }, + annotationRepresentatives: { + height: theme.typography.body2.normal.lineHeight, + }, + } as const; +} diff --git a/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentPanelHeader/DocumentPanelHeader.tsx b/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentPanelHeader/DocumentPanelHeader.tsx index b81e89b00..3d8e53c49 100644 --- a/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentPanelHeader/DocumentPanelHeader.tsx +++ b/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentPanelHeader/DocumentPanelHeader.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { useDocumentViewerModeHandler } from '../../../../../services/documentViewerMode'; import { AnnotationHeader } from './AnnotationHeader'; import { OccurrenceHeader } from './OccurrenceHeader'; +import { ChecklistHeader } from './ChecklistHeader'; export { DocumentPanelHeader }; @@ -14,5 +15,8 @@ function DocumentPanelHeader() { case 'occurrence': const { entityId, category } = documentViewerModeHandler.documentViewerMode; return ; + case 'checklist': + const { message } = documentViewerModeHandler.documentViewerMode.check; + return ; } } diff --git a/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentViewer.tsx b/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentViewer.tsx index b5e70b16d..71aa4a1ab 100644 --- a/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentViewer.tsx +++ b/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/DocumentViewer.tsx @@ -202,28 +202,7 @@ function DocumentViewer(props: { splittedTextByLine: splittedTextByLineType }): case 'occurrence': const { entityLineNumbers } = documentViewerModeHandler.documentViewerMode; const detectedLines = props.splittedTextByLine.filter(({ line }) => entityLineNumbers.includes(line)); - const displayedLines: splittedTextByLineType = []; - for (const detectedLine of detectedLines) { - const addedLinesNumber: number[] = []; - while ( - getLinesLengthByLineNumbers([detectedLine.line, ...addedLinesNumber]) < 30 && - addedLinesNumber.length < 5 - ) { - addedLinesNumber.push( - detectedLine.line + (addedLinesNumber.length / 2 + 1), - detectedLine.line - (addedLinesNumber.length / 2 + 1), - ); - } - displayedLines.push(detectedLine, ...getLinesByLineNumbers(addedLinesNumber)); - } - const displayedUniqueLines: splittedTextByLineType = []; - displayedLines.forEach(function (displayedLine) { - const doubledLinesCount = displayedUniqueLines.findIndex((line) => line.line == displayedLine.line); - if (doubledLinesCount <= -1) { - displayedUniqueLines.push(displayedLine); - } - }); - return displayedUniqueLines.sort((line1, line2) => line1.line - line2.line); + return getDisplayedUniqueLines(detectedLines); case 'annotation': switch (document.route) { @@ -239,9 +218,42 @@ function DocumentViewer(props: { splittedTextByLine: splittedTextByLineType }): default: return props.splittedTextByLine; } + + case 'checklist': + const { checkLineNumbers } = documentViewerModeHandler.documentViewerMode; + const detectedCheckLines = props.splittedTextByLine.filter(({ line }) => checkLineNumbers.includes(line)); + return getDisplayedUniqueLines(detectedCheckLines); } } + function getDisplayedUniqueLines(detectedLines: splittedTextByLineType): splittedTextByLineType { + const displayedLines: splittedTextByLineType = []; + + detectedLines.forEach((detectedLine) => { + const addedLinesNumber: number[] = []; + while ( + getLinesLengthByLineNumbers([detectedLine.line, ...addedLinesNumber]) < 30 && + addedLinesNumber.length < 5 + ) { + addedLinesNumber.push( + detectedLine.line + (addedLinesNumber.length / 2 + 1), + detectedLine.line - (addedLinesNumber.length / 2 + 1), + ); + } + displayedLines.push(detectedLine, ...getLinesByLineNumbers(addedLinesNumber)); + }); + + const displayedUniqueLines: splittedTextByLineType = []; + displayedLines.forEach(function (displayedLine) { + const doubledLinesCount = displayedUniqueLines.findIndex((line) => line.line == displayedLine.line); + if (doubledLinesCount <= -1) { + displayedUniqueLines.push(displayedLine); + } + }); + + return displayedUniqueLines.sort((line1, line2) => line1.line - line2.line); + } + function getLinesByLineNumbers(lineNumbers: number[]): splittedTextByLineType { return props.splittedTextByLine.filter(({ line }) => lineNumbers.includes(line)); } diff --git a/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/lib/getAnnotationTextDisplayStyle.ts b/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/lib/getAnnotationTextDisplayStyle.ts index 1bc08439f..dc1862131 100644 --- a/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/lib/getAnnotationTextDisplayStyle.ts +++ b/packages/generic/client/src/pages/Home/DocumentAnnotator/DocumentPanel/lib/getAnnotationTextDisplayStyle.ts @@ -32,6 +32,8 @@ function getAnnotationTextDisplayStyle({ } else { return 'outlined'; } + case 'checklist': + return 'filled'; } } } diff --git a/packages/generic/client/src/services/documentViewerMode/DocumentViewerModeHandlerContextProvider.tsx b/packages/generic/client/src/services/documentViewerMode/DocumentViewerModeHandlerContextProvider.tsx index c7411386d..a302c07ae 100644 --- a/packages/generic/client/src/services/documentViewerMode/DocumentViewerModeHandlerContextProvider.tsx +++ b/packages/generic/client/src/services/documentViewerMode/DocumentViewerModeHandlerContextProvider.tsx @@ -8,6 +8,7 @@ const DocumentViewerModeHandlerContext = createContext false, resetViewerMode: () => null, setOccurrenceMode: () => null, + setChecklistMode: () => null, switchAnonymizedView: () => null, documentViewerMode: DEFAULT_VIEWER_MODE, }); diff --git a/packages/generic/client/src/services/documentViewerMode/buildDocumentViewerModeHandler.ts b/packages/generic/client/src/services/documentViewerMode/buildDocumentViewerModeHandler.ts index b8a200eff..79243a8c5 100644 --- a/packages/generic/client/src/services/documentViewerMode/buildDocumentViewerModeHandler.ts +++ b/packages/generic/client/src/services/documentViewerMode/buildDocumentViewerModeHandler.ts @@ -1,4 +1,4 @@ -import { annotationType } from '@label/core'; +import { annotationReportType, annotationType } from '@label/core'; import { viewerModeType } from './viewerMode'; export { buildDocumentViewerModeHandler }; @@ -13,6 +13,7 @@ type documentViewerModeHandlerType = { entityId: annotationType['entityId'], entityLineNumbers: number[], ) => void; + setChecklistMode: (check: annotationReportType['checklist'][number], entitiesLineNumbers: number[]) => void; switchAnonymizedView: () => void; documentViewerMode: viewerModeType; }; @@ -24,6 +25,7 @@ function buildDocumentViewerModeHandler( return { isAnonymizedView, setOccurrenceMode, + setChecklistMode, switchAnonymizedView, documentViewerMode, resetViewerMode, @@ -54,6 +56,15 @@ function buildDocumentViewerModeHandler( }); } + function setChecklistMode(check: annotationReportType['checklist'][number], checkLineNumbers: number[]) { + setViewerMode({ + kind: 'checklist', + check, + checkLineNumbers, + isAnonymizedView: documentViewerMode.isAnonymizedView, + }); + } + function switchAnonymizedView() { setViewerMode({ ...documentViewerMode, isAnonymizedView: !documentViewerMode.isAnonymizedView }); } diff --git a/packages/generic/client/src/services/documentViewerMode/viewerMode.ts b/packages/generic/client/src/services/documentViewerMode/viewerMode.ts index 2664c73f4..4679f4f0d 100644 --- a/packages/generic/client/src/services/documentViewerMode/viewerMode.ts +++ b/packages/generic/client/src/services/documentViewerMode/viewerMode.ts @@ -1,4 +1,4 @@ -import { annotationType } from '@label/core'; +import { annotationReportType, annotationType } from '@label/core'; export { DEFAULT_VIEWER_MODE }; @@ -12,6 +12,12 @@ type viewerModeType = entityId: annotationType['entityId']; entityLineNumbers: number[]; isAnonymizedView: boolean; + } + | { + kind: 'checklist'; + check: annotationReportType['checklist'][number]; + checkLineNumbers: number[]; + isAnonymizedView: boolean; }; const DEFAULT_VIEWER_MODE: viewerModeType = { diff --git a/packages/generic/client/src/wordings/fr.ts b/packages/generic/client/src/wordings/fr.ts index c6deb2bc1..af1686b34 100644 --- a/packages/generic/client/src/wordings/fr.ts +++ b/packages/generic/client/src/wordings/fr.ts @@ -202,7 +202,7 @@ const fr = { annotationGuide: "Guide d'annotation", category: 'Catégorie', changeCategory: 'Changer de catégorie', - checklist: 'Mises en doute : ', + checklist: 'Mises en doute', close: 'Fermer', describeTheProblem: 'Décrivez le problème. Soyez exhaustif.', delete: 'Supprimer', diff --git a/packages/generic/core/src/modules/annotationReport/annotationReportType.ts b/packages/generic/core/src/modules/annotationReport/annotationReportType.ts index b78a62ad8..1d949da76 100644 --- a/packages/generic/core/src/modules/annotationReport/annotationReportType.ts +++ b/packages/generic/core/src/modules/annotationReport/annotationReportType.ts @@ -8,7 +8,57 @@ export type { annotationReportType }; const annotationReportModel = buildModel({ kind: 'object', content: { - checklist: { kind: 'array', content: { kind: 'primitive', content: 'string' } }, + checklist: { + kind: 'array', + content: { + kind: 'object', + content: { + checkType: { + kind: 'primitive', + content: 'string', + }, + message: { kind: 'primitive', content: 'string' }, + entities: { + kind: 'array', + content: { + kind: 'object', + content: { + text: { kind: 'primitive', content: 'string' }, + start: { kind: 'primitive', content: 'number' }, + category: { kind: 'primitive', content: 'string' }, + source: { kind: 'primitive', content: 'string' }, + score: { kind: 'primitive', content: 'number' }, + entityId: { kind: 'primitive', content: 'string' }, + end: { kind: 'primitive', content: 'number' }, + }, + }, + }, + sentences: { + kind: 'or', + content: [ + { + kind: 'array', + content: { + kind: 'object', + content: { + start: { kind: 'primitive', content: 'number' }, + end: { kind: 'primitive', content: 'number' }, + }, + }, + }, + { kind: 'primitive', content: 'undefined' }, + ], + }, + metadata_text: { + kind: 'or', + content: [ + { kind: 'array', content: { kind: 'primitive', content: 'string' } }, + { kind: 'primitive', content: 'undefined' }, + ], + }, + }, + }, + }, documentId: { kind: 'custom', content: 'id' }, _id: { kind: 'custom', content: 'id' }, }, diff --git a/yarn.lock b/yarn.lock index 6d648e1ba..81888cf75 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12930,9 +12930,9 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -"pelta-design-system@https://github.com/Cour-de-cassation/pelta-design-system#e508f818ac443ad048e5cd1b74f42ec7f0bd046d": +"pelta-design-system@https://github.com/Cour-de-cassation/pelta-design-system#b9d52684da753a250ba351588c31da7bc7280cbe": version "0.0.1" - resolved "https://github.com/Cour-de-cassation/pelta-design-system#e508f818ac443ad048e5cd1b74f42ec7f0bd046d" + resolved "https://github.com/Cour-de-cassation/pelta-design-system#b9d52684da753a250ba351588c31da7bc7280cbe" dependencies: "@material-ui/core" "^4.12.3" "@material-ui/icons" "^4.11.2"