feat: stat Délai moyen entre MED et (Gendarmerie ou Procureur)

améliore stats délai en introduisant une notion de "procedéure"
This commit is contained in:
Sébastien Arod 2025-05-29 23:41:27 +02:00
parent cc16a203a0
commit 4433ae466f
7 changed files with 219 additions and 61 deletions

View file

@ -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",

View file

@ -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;
}

View file

@ -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";
}

View file

@ -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"
);
}

View file

@ -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"
);
}

View file

@ -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);
}

View file

@ -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"
);
}