Skip to content

Commit

Permalink
essaye de parser les noms de résidu
Browse files Browse the repository at this point in the history
  • Loading branch information
vmaubert committed Dec 23, 2024
1 parent 8d3a342 commit 33cfc99
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 28 deletions.
58 changes: 47 additions & 11 deletions server/services/imapService/girpa.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { describe, expect, test } from 'vitest';
import { analyseXmlValidator, extractSample } from './girpa';
import {
analyseXmlValidator,
extractSample,
getResidue,
residueCasNumberValidator,
residueEnglishNameValidator
} from './girpa';
import { z } from 'zod';

const girpaXMLExample = (analyses: z.input<typeof analyseXmlValidator>[]) => ({
Expand Down Expand Up @@ -41,7 +47,8 @@ describe('parse correctement le XML', () => {
LMR: 10,
Limite_de_quantification: '1,1',
Résultat: '5,2',
Substance_active_CAS: '?'
Substance_active_CAS: '?',
Substance_active_anglais: 'bixafen'
}]))).toMatchInlineSnapshot(`
[
{
Expand All @@ -52,7 +59,10 @@ describe('parse correctement le XML', () => {
"lmr": 10,
"result": 5.2,
"result_kind": "Q",
"substance": "?",
"substance": {
"kind": "SimpleResidue",
"value": "RF-1056-001-PPP",
},
},
],
},
Expand All @@ -66,47 +76,73 @@ describe('parse correctement le XML', () => {
LMR: 10,
Limite_de_quantification: '0,9',
Résultat: '0,3',
Substance_active_CAS: '1967-25-5'
Substance_active_CAS: '1967-25-5',
Substance_active_anglais: 'bixafen'
},
{
LMR: 10,
Limite_de_quantification: '0,9',
Résultat: '0,29',
Substance_active_CAS: '27112-32-9'
Substance_active_CAS: '27112-32-9',
Substance_active_anglais: 'fluopyram'
},
{
LMR: 10,
Limite_de_quantification: '1',
Résultat: '10,1',
Substance_active_CAS: '15299-99-7'
Substance_active_CAS: '15299-99-7',
Substance_active_anglais: 'fluroxypyr'
},
{
LMR: '-',
Limite_de_quantification: '1',
Résultat: '8',
Substance_active_CAS: '?'
Substance_active_CAS: '?',
Substance_active_anglais: 'fluxapyroxad'
}
]))![0].substances).toMatchInlineSnapshot(`
[
{
"lmr": 10,
"result": 0.3,
"result_kind": "Q",
"substance": "1967-25-5",
"substance": {
"kind": "SimpleResidue",
"value": "RF-1056-001-PPP",
},
},
{
"lmr": 10,
"result": 10.1,
"result_kind": "Q",
"substance": "15299-99-7",
"substance": {
"kind": "Analyte",
"value": "RF-0215-003-PPP",
},
},
{
"lmr": null,
"result": null,
"result_kind": "NQ",
"substance": "?",
"substance": {
"kind": "SimpleResidue",
"value": "RF-00000024-PAR",
},
},
]
`);
});
});
});


describe('getResidue', () => {
test.each<[string, string, ReturnType<typeof getResidue>]>([
['', 'toto', null],
['', 'bixafen', {value: 'RF-1056-001-PPP', kind: 'SimpleResidue'}],
['', 'bixafen according reg.', {value: 'RF-1056-001-PPP', kind: 'SimpleResidue'}],
])('getResidue %#', (casNumber, englishName, expected) => {

expect(getResidue(casNumber as z.infer<typeof residueCasNumberValidator>, englishName as z.infer<typeof residueEnglishNameValidator>)).toEqual(expected)

})
})
104 changes: 88 additions & 16 deletions server/services/imapService/girpa.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,118 @@
import { XMLParser } from 'fast-xml-parser';
import { entries } from 'lodash';
import { z } from 'zod';
import { ExportDataFromEmail, ExportDataSubstance, ExportSample, IsSender, LaboratoryConf } from './index';
import { Analyte } from '../../../shared/referential/Residue/Analyte';
import { AnalyteLabels } from '../../../shared/referential/Residue/AnalyteLabels';
import { ComplexResidue } from '../../../shared/referential/Residue/ComplexResidue';
import { ComplexResidueLabels } from '../../../shared/referential/Residue/ComplexResidueLabels';
import { SimpleResidue } from '../../../shared/referential/Residue/SimpleResidue';
import { SimpleResidueLabels } from '../../../shared/referential/Residue/SimpleResidueLabels';
import {
ExportDataFromEmail,
ExportDataSubstance,
ExportResidue,
ExportSample,
IsSender,
LaboratoryConf
} from './index';

const isSender: IsSender = (_emailSender) => true;

// Visible for testing
export const getResidue = (
_casNumber: ResidueCasNumber,
englishName: ResidueEnglishName
): ExportResidue | null => {
const normalizedEnglishName = englishName
.toLowerCase()
.replace(' according reg.', '');

for (const entry of entries(SimpleResidueLabels)) {
if (entry[1].toLowerCase() === normalizedEnglishName) {
return { value: entry[0] as SimpleResidue, kind: 'SimpleResidue' };
}
}

for (const entry of entries(ComplexResidueLabels)) {
if (entry[1].toLowerCase() === normalizedEnglishName) {
return { value: entry[0] as ComplexResidue, kind: 'ComplexResidue' };
}
}

for (const entry of entries(AnalyteLabels)) {
if (entry[1].toLowerCase() === normalizedEnglishName) {
return { value: entry[0] as Analyte, kind: 'Analyte' };
}
}
return null;
};

const frenchNumberStringValidator = z
.string()
.transform((val) => Number(`${val}`.replace(',', '.')))
.pipe(z.number());

export const residueCasNumberValidator = z.string().brand('CAS number');
type ResidueCasNumber = z.infer<typeof residueCasNumberValidator>;

export const residueEnglishNameValidator = z
.string()
.brand('ResidueEnglishName');
type ResidueEnglishName = z.infer<typeof residueEnglishNameValidator>;

export const analyseXmlValidator = z.object({
Résultat: frenchNumberStringValidator,
Limite_de_quantification: frenchNumberStringValidator,
LMR: z.union([z.literal('-'), z.number(), frenchNumberStringValidator]),
Substance_active_CAS: z.string(),
//Substance_active_anglais: z.string()
Substance_active_CAS: residueCasNumberValidator,
Substance_active_anglais: residueEnglishNameValidator
});
// Visible for testing
export const extractSample = (obj: unknown): ExportSample[] | null => {
const echantillonValidator = z.object({
Code_échantillon: z.string(),
Commentaire: z.string(),
Analyse: z.array(
analyseXmlValidator
)
Analyse: z.array(analyseXmlValidator)
});
const validator = z.object({
Rapport: z.object({
Echantillon: z.union([echantillonValidator.transform(e => ([e])), z.array(echantillonValidator)])
Echantillon: z.union([
echantillonValidator.transform((e) => [e]),
z.array(echantillonValidator)
])
})
});

const result = validator.safeParse(obj);

if (result.success) {
return result.data.Rapport.Echantillon.map(echantillon => {
return result.data.Rapport.Echantillon.map((echantillon) => {
const substances: ExportDataSubstance[] = echantillon.Analyse.filter(
(a) => a.LMR === '-' || a.Résultat > a.LMR || a.Résultat >= a.Limite_de_quantification / 3
).map(a => {
(a) =>
a.LMR === '-' ||
a.Résultat > a.LMR ||
a.Résultat >= a.Limite_de_quantification / 3
)
.map((a) => {
const substance = getResidue(
a.Substance_active_CAS,
a.Substance_active_anglais
);
if (substance === null) {
//FIXME comment gérer les erreurs ?!
return null;
}

return a.LMR === '-' ? ({result_kind: 'NQ', result: null, lmr: null, substance: a.Substance_active_CAS}) : ({result_kind: 'Q', result: a.Résultat, lmr: a.LMR, substance: a.Substance_active_CAS})
});
return a.LMR === '-'
? {
result_kind: 'NQ',
result: null,
lmr: null,
substance
}
: { result_kind: 'Q', result: a.Résultat, lmr: a.LMR, substance };
})
.filter((s): s is ExportDataSubstance => s !== null);
return {
sampleReference: echantillon['Code_échantillon'],
notes: echantillon.Commentaire,
Expand All @@ -54,16 +126,16 @@ export const extractSample = (obj: unknown): ExportSample[] | null => {

const exportDataFromEmail: ExportDataFromEmail = (email) => {
const xmlFile = email.attachments.find(
({ contentType }) => contentType === 'text/xml' || contentType === 'application/xml'
({ contentType }) =>
contentType === 'text/xml' || contentType === 'application/xml'
);

if (xmlFile !== undefined) {
const parser = new XMLParser();
const obj = parser.parse(xmlFile.content);
console.log(obj)
return extractSample(obj);
}else{
console.log("Aucun XML", email.attachments)
} else {
console.log('Aucun XML', email.attachments);
}

return null;
Expand Down
9 changes: 8 additions & 1 deletion server/services/imapService/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@ import { LaboratoryName } from '../../../shared/referential/Laboratory';
import config from '../../utils/config';
import { girpaConf } from './girpa';
import { Sample } from '../../../shared/schema/Sample/Sample';
import { SimpleResidue } from '../../../shared/referential/Residue/SimpleResidue';
import { ComplexResidue } from '../../../shared/referential/Residue/ComplexResidue';
import { Analyte } from '../../../shared/referential/Residue/Analyte';

const laboratoriesWithConf = ['GIR 49'] as const satisfies LaboratoryName[];
type LaboratoryWithConf = (typeof laboratoriesWithConf)[number];

export type ExportResidue =
{ value: SimpleResidue, kind: 'SimpleResidue' } |
{ value:ComplexResidue, kind: 'ComplexResidue' } |
{ value: Analyte, kind: 'Analyte' }

export type ExportDataSubstance = {substance: string} & ( {result_kind: 'NQ', result: null, lmr: null} | {result_kind: 'Q', result: number, lmr: number})
export type ExportDataSubstance = {substance: ExportResidue} & ( {result_kind: 'NQ', result: null, lmr: null} | {result_kind: 'Q', result: number, lmr: number})
export type IsSender = (senderAddress: string) => boolean
export type ExportSample = {
sampleReference: Sample['reference'],
Expand Down

0 comments on commit 33cfc99

Please sign in to comment.