From 4433ae466f93f9f38ed2b12cc73e0f0d285efeaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Arod?= Date: Thu, 29 May 2025 23:41:27 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20stat=20D=C3=A9lai=20moyen=20entre=20MED?= =?UTF-8?q?=20et=20(Gendarmerie=20ou=20Procureur)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit améliore stats délai en introduisant une notion de "procedéure" --- src/statistiques/v2/penales/StatsPenales.ts | 5 ++ ...omputeFamilleAvecInfosProceduresPenales.ts | 78 ++++++++++++++++++ .../v2/penales/computeStatsPenales.ts | 80 +++++-------------- .../computeIntervalGendarmerieProcureur.ts | 24 ++++++ ...omputeIntervalMedGendarmerieOuProcureur.ts | 34 ++++++++ .../computeIntervalProcedurePenale.ts | 32 ++++++++ ...eIntervalProcureurTribunalCorrectionnel.ts | 27 +++++++ 7 files changed, 219 insertions(+), 61 deletions(-) create mode 100644 src/statistiques/v2/penales/computeFamilleAvecInfosProceduresPenales.ts create mode 100644 src/statistiques/v2/penales/intervals/computeIntervalGendarmerieProcureur.ts create mode 100644 src/statistiques/v2/penales/intervals/computeIntervalMedGendarmerieOuProcureur.ts create mode 100644 src/statistiques/v2/penales/intervals/computeIntervalProcedurePenale.ts create mode 100644 src/statistiques/v2/penales/intervals/computeIntervalProcureurTribunalCorrectionnel.ts diff --git a/src/statistiques/v2/penales/StatsPenales.ts b/src/statistiques/v2/penales/StatsPenales.ts index fef774e..1b723b9 100644 --- a/src/statistiques/v2/penales/StatsPenales.ts +++ b/src/statistiques/v2/penales/StatsPenales.ts @@ -134,6 +134,11 @@ export const statsPenalesDesc = { }, }, }, + intervalMedGendarmerieOuProcureur: { + label: "Délai moyen entre MED et (Gendarmerie ou Procureur)", + unit: " jours", + valueMaxFractioDigits: 0, + }, intervalGendarmerieProcureur: { label: "Délai moyen entre Gendarmerie et Procureur", unit: " jours", diff --git a/src/statistiques/v2/penales/computeFamilleAvecInfosProceduresPenales.ts b/src/statistiques/v2/penales/computeFamilleAvecInfosProceduresPenales.ts new file mode 100644 index 0000000..3a7858d --- /dev/null +++ b/src/statistiques/v2/penales/computeFamilleAvecInfosProceduresPenales.ts @@ -0,0 +1,78 @@ +import { isEmpty } from "lodash"; +import { + EvenementFamille, + isGendarmerie, + isProcureur, +} from "../../../data/EvenementFamille"; +import { isTribunalCorrectionnel } from "./computeStatsPenales"; +import { Famille } from "../../../data/Famille"; +import { differenceInDays } from "date-fns"; + +export interface InfosProcedurePenale { + evtMiseEnDemeure?: EvenementFamille; + evtGendarmerie?: EvenementFamille; + evtProcureur?: EvenementFamille; + evtTribunalCorrectionnel?: EvenementFamille; +} + +export interface FamilleAvecInfosProceduresPenales extends Famille { + proceduresPenales: InfosProcedurePenale[]; +} + +export function computeFamilleAvecInfosProceduresPenales( + famille: Famille +): FamilleAvecInfosProceduresPenales { + const proceduresPenales = computeProceduresPenales(famille.Evenements); + return { + ...famille, + proceduresPenales: proceduresPenales, + }; +} + +function computeProceduresPenales( + evenenemts: EvenementFamille[] +): InfosProcedurePenale[] { + const procedures: InfosProcedurePenale[] = []; + let currentProcedure: InfosProcedurePenale = {}; + for (let i = 0; i < evenenemts.length; i++) { + const evt = evenenemts[i]; + if (evt.Type === "Mise en demeure de scolarisation") { + if (currentProcedure.evtMiseEnDemeure) { + // Si l'événement Mise en demeure est déjà présent, on considère que c'est une nouvelle procédure + procedures.push(currentProcedure); + currentProcedure = {}; + } + currentProcedure.evtMiseEnDemeure = evt; + } else if (isGendarmerie(evt)) { + if ( + currentProcedure.evtGendarmerie && + evt.Date && + currentProcedure.evtGendarmerie.Date && + differenceInDays(evt.Date, currentProcedure.evtGendarmerie.Date) > 60 + ) { + // Si l'événement Gendarmerie est déjà présent et les 2 evt gendarmerie sont séparé de plus de 60 jours + // on considère que c'est une nouvelle procédure + procedures.push(currentProcedure); + currentProcedure = {}; + } + currentProcedure.evtGendarmerie = evt; + } else if (isProcureur(evt)) { + if (currentProcedure.evtProcureur) { + // Si l'événement Procureur est déjà présent, on considère que c'est une nouvelle procédure + procedures.push(currentProcedure); + currentProcedure = {}; + } + currentProcedure.evtProcureur = evt; + } else if (isTribunalCorrectionnel(evt)) { + // Le tribunal correctionnel est le dernier événement d'une procédure pénale + currentProcedure.evtTribunalCorrectionnel = evt; + procedures.push(currentProcedure); + currentProcedure = {}; + } + } + + if (!isEmpty(currentProcedure)) { + procedures.push(currentProcedure); + } + return procedures; +} diff --git a/src/statistiques/v2/penales/computeStatsPenales.ts b/src/statistiques/v2/penales/computeStatsPenales.ts index 25903e7..faf4ac4 100644 --- a/src/statistiques/v2/penales/computeStatsPenales.ts +++ b/src/statistiques/v2/penales/computeStatsPenales.ts @@ -1,15 +1,12 @@ -import { differenceInDays } from "date-fns"; import { isCompositionPenale, isCRPC, - isProcureur, isGendarmerie, isEvtProcedurePenale, EvenementFamille, isProcedurePenaleHorsGendarmerie, } from "../../../data/EvenementFamille"; import { Famille, isExResistant, isResistant } from "../../../data/Famille"; -import { average } from "../../../utils/math/average"; import { filterFamillesWithOneOfEvenements } from "../filterFamillesWithOneOfEvenements"; import { filterFamillesWithOneOfEvenementsOfType } from "../filterFamillesWithOneOfEvenementsOfType"; import { StatsPenales } from "./StatsPenales"; @@ -18,6 +15,13 @@ import { buildInfoTribunauxCorrectionnel } from "./tc/buildInfoTribunauxCorrecti import { InfoTribunalCorrectionnel } from "./tc/InfoTribunalCorrectionnel"; import { computeTribunalCorrectionnel } from "./tc/computeTribunalCorrectionnel"; import { computeTribunalPolice } from "./tp/computeTribunalPolice"; +import { computeIntervalGendarmerieProcureur } from "./intervals/computeIntervalGendarmerieProcureur"; +import { computeIntervalProcureurTribunalCorrectionnel } from "./intervals/computeIntervalProcureurTribunalCorrectionnel"; +import { + computeFamilleAvecInfosProceduresPenales, + FamilleAvecInfosProceduresPenales as FamilleAvecInfoProceduresPenales, +} from "./computeFamilleAvecInfosProceduresPenales"; +import { computeIntervalMedGendarmerieOuProcureur } from "./intervals/computeIntervalMedGendarmerieOuProcureur"; export type FamilleAvecInfoTribunaux = Famille & { infoTribunaux: InfoTribunalCorrectionnel[]; @@ -60,6 +64,9 @@ export function computeStatsPenales(familles: Famille[]): StatsPenales { }; }); + const famillesAvecInfoProceduresPenales: FamilleAvecInfoProceduresPenales[] = + famillesResistantesOuEx.map(computeFamilleAvecInfosProceduresPenales); + const statsPenales: StatsPenales = { nbFamillesMisesEnDemeure: nbFamillesAvecPagesLiees(famillesMisesEnDemeure), nbFamillesAvecProcedurePenale: nbFamillesAvecPagesLiees( @@ -136,10 +143,17 @@ export function computeStatsPenales(familles: Famille[]): StatsPenales { ), tribunalDePolice: computeTribunalPolice(famillesResistantesOuEx), + intervalMedGendarmerieOuProcureur: computeIntervalMedGendarmerieOuProcureur( + famillesAvecInfoProceduresPenales + ), - intervalGendarmerieProcureur: computeIntervalGendarmerieProcureur(familles), + intervalGendarmerieProcureur: computeIntervalGendarmerieProcureur( + famillesAvecInfoProceduresPenales + ), intervalProcureurTribunalCorrectionnel: - computeIntervalProcureurTribunalCorrectionnel(familles), + computeIntervalProcureurTribunalCorrectionnel( + famillesAvecInfoProceduresPenales + ), }; return statsPenales; } @@ -190,62 +204,6 @@ function computeCompositionPenales( }; } -function computeIntervalGendarmerieProcureur(familles: Famille[]): number { - const intervals = familles.flatMap((f) => { - const evtGendarmerie = f.EvenementsEL.find((e) => isGendarmerie(e)); - const evtProcureur = f.EvenementsEL.find((e) => isProcureur(e)); - - // consider only intervals for families with both events date - if (!evtGendarmerie?.Date || !evtProcureur?.Date) { - return []; - } - - const intervalInDays = differenceInDays( - evtProcureur.Date, - evtGendarmerie.Date - ); - if (intervalInDays < 0) { - console.warn( - `IntervalGendarmerieProcureur < 0 pour ${f.Titre} (${f.notionId}): Cet interval sera ignoré.` - ); - return []; - } else { - return [intervalInDays]; - } - }); - return average(intervals); -} - -function computeIntervalProcureurTribunalCorrectionnel( - familles: Famille[] -): number { - const intervals = familles.flatMap((f) => { - const evtProcureur = f.EvenementsEL.find((e) => isProcureur(e)); - const evtTribunal = f.EvenementsEL.find( - (e) => e.Type === "Tribunal correctionnel" - ); - - // consider only intervals for families with both events date - if (!evtProcureur?.Date || !evtTribunal?.Date) { - return []; - } - - const intervalInDays = differenceInDays( - evtTribunal.Date, - evtProcureur.Date - ); - if (intervalInDays < 0) { - console.warn( - `IntervalProcureurTribunalCorrectionnel < 0 for ${f.Titre} (${f.notionId})` - ); - return []; - } else { - return [intervalInDays]; - } - }); - return average(intervals); -} - export function isTribunalCorrectionnel(e: EvenementFamille): boolean { return e.Type === "Tribunal correctionnel"; } diff --git a/src/statistiques/v2/penales/intervals/computeIntervalGendarmerieProcureur.ts b/src/statistiques/v2/penales/intervals/computeIntervalGendarmerieProcureur.ts new file mode 100644 index 0000000..4a21254 --- /dev/null +++ b/src/statistiques/v2/penales/intervals/computeIntervalGendarmerieProcureur.ts @@ -0,0 +1,24 @@ +import { differenceInDays } from "date-fns"; +import { computeIntervalProcedurePenale } from "./computeIntervalProcedurePenale"; +import { + FamilleAvecInfosProceduresPenales, + InfosProcedurePenale, +} from "../computeFamilleAvecInfosProceduresPenales"; + +export function computeIntervalGendarmerieProcureur( + familles: FamilleAvecInfosProceduresPenales[] +): number { + return computeIntervalProcedurePenale( + familles, + (procPenal: InfosProcedurePenale): number | undefined => { + if (procPenal.evtGendarmerie?.Date && procPenal.evtProcureur?.Date) { + return differenceInDays( + procPenal.evtProcureur.Date, + procPenal.evtGendarmerie.Date + ); + } + return undefined; + }, + "IntervalGendarmerieProcureur" + ); +} diff --git a/src/statistiques/v2/penales/intervals/computeIntervalMedGendarmerieOuProcureur.ts b/src/statistiques/v2/penales/intervals/computeIntervalMedGendarmerieOuProcureur.ts new file mode 100644 index 0000000..cd30f63 --- /dev/null +++ b/src/statistiques/v2/penales/intervals/computeIntervalMedGendarmerieOuProcureur.ts @@ -0,0 +1,34 @@ +import { differenceInDays } from "date-fns"; +import { + FamilleAvecInfosProceduresPenales as FamilleAvecInfoProceduresPenales, + InfosProcedurePenale, +} from "../computeFamilleAvecInfosProceduresPenales"; +import { computeIntervalProcedurePenale } from "./computeIntervalProcedurePenale"; +import { min } from "lodash"; + +export function computeIntervalMedGendarmerieOuProcureur( + familles: FamilleAvecInfoProceduresPenales[] +): number { + return computeIntervalProcedurePenale( + familles, + (procPenal: InfosProcedurePenale): number | undefined => { + if ( + procPenal.evtMiseEnDemeure?.Date && + (procPenal.evtGendarmerie?.Date || procPenal.evtProcureur?.Date) + ) { + const earliestGendarmerieOuProcureur: Date = min( + [procPenal.evtGendarmerie?.Date, procPenal.evtProcureur?.Date].filter( + (d) => d !== undefined && d !== null + ) + ) as Date; + + return differenceInDays( + earliestGendarmerieOuProcureur, + procPenal.evtMiseEnDemeure.Date + ); + } + return undefined; + }, + "IntervalMedGendarmerieOuProcureur" + ); +} diff --git a/src/statistiques/v2/penales/intervals/computeIntervalProcedurePenale.ts b/src/statistiques/v2/penales/intervals/computeIntervalProcedurePenale.ts new file mode 100644 index 0000000..06cf6e3 --- /dev/null +++ b/src/statistiques/v2/penales/intervals/computeIntervalProcedurePenale.ts @@ -0,0 +1,32 @@ +import { Famille } from "../../../../data/Famille"; +import { average } from "../../../../utils/math/average"; +import { + FamilleAvecInfosProceduresPenales as FamilleAvecInfoProceduresPenales, + InfosProcedurePenale, +} from "../computeFamilleAvecInfosProceduresPenales"; + +export function computeIntervalProcedurePenale( + famillesAvecInfoProceduresPenales: FamilleAvecInfoProceduresPenales[], + // Function to extract the interval from each procedure returns undefined if not applicable + intervalFn: ( + procPenal: InfosProcedurePenale, + famille: Famille + ) => number | undefined, + intervalName: string +): number { + const intervals: number[] = famillesAvecInfoProceduresPenales.flatMap((f) => { + return f.proceduresPenales + .map((pp) => { + const interval = intervalFn(pp, f); + if (interval !== undefined && interval < 0) { + console.warn( + `${intervalName} < 0 pour ${f.Titre} (${f.notionId}): Cet interval sera ignoré.` + ); + return undefined; + } + return interval; + }) + .filter((i) => i !== undefined) as number[]; + }); + return average(intervals); +} diff --git a/src/statistiques/v2/penales/intervals/computeIntervalProcureurTribunalCorrectionnel.ts b/src/statistiques/v2/penales/intervals/computeIntervalProcureurTribunalCorrectionnel.ts new file mode 100644 index 0000000..9847164 --- /dev/null +++ b/src/statistiques/v2/penales/intervals/computeIntervalProcureurTribunalCorrectionnel.ts @@ -0,0 +1,27 @@ +import { differenceInDays } from "date-fns"; +import { computeIntervalProcedurePenale } from "./computeIntervalProcedurePenale"; +import { + FamilleAvecInfosProceduresPenales, + InfosProcedurePenale, +} from "../computeFamilleAvecInfosProceduresPenales"; + +export function computeIntervalProcureurTribunalCorrectionnel( + familles: FamilleAvecInfosProceduresPenales[] +): number { + return computeIntervalProcedurePenale( + familles, + (procPenal: InfosProcedurePenale): number | undefined => { + if ( + procPenal.evtProcureur?.Date && + procPenal.evtTribunalCorrectionnel?.Date + ) { + return differenceInDays( + procPenal.evtTribunalCorrectionnel.Date, + procPenal.evtProcureur.Date + ); + } + return undefined; + }, + "IntervalProcureurTribunalCorrectionnel" + ); +}