remove proc pénale

wip-related-pages
sebastien.arod@gmail.com 2024-09-05 09:23:27 +02:00
parent 21f50efb5a
commit b4d1a7edad
32 changed files with 501 additions and 65 deletions

3
.gitignore vendored
View File

@ -16,5 +16,4 @@
dist
test-coverage
node_modules
el-stats-par-anciennete.json
el-stats.json
el-stats*.json

View File

@ -90,6 +90,7 @@ export function isEvenementInPeriod(
export function isEvenementBefore(evt: EvenementFamille, date: Date): boolean {
if (evt.Date === null) {
// Assume lack of date are oldest events
return true;
}
return evt.Date < date;
@ -101,3 +102,18 @@ export function isValidEvenementFamille(str: string | null): boolean {
Object.prototype.hasOwnProperty.call(categorieEvenement, str)
);
}
export function isGendarmerie(e: EvenementFamille): boolean {
return (
e.Type === "Audition gendarmerie / police" ||
e.Type === "Gendarmerie/Forces de l'ordre" ||
e.Type === "Récidive gendarmerie" ||
e.Type === "Passage police municipale"
);
}
export function isEvtProcureur(e: EvenementFamille): boolean {
return (
e.Type === "Audition procureur" ||
e.Type === "Audience CRPC" ||
e.Type === "Convocation CRPC"
);
}

View File

@ -13,6 +13,7 @@ export type Famille = {
Integration: Date | null;
ContexteEntree: ContexteEntreeDC;
Sortie: Date | null;
// sorted by date asc
Evenements: EvenementFamille[];
};

View File

@ -0,0 +1,4 @@
export type EvolFormatOptions = {
evolMaxFractioDigits?: number;
evolPctMaxFractioDigits?: number;
};

View File

@ -0,0 +1,4 @@
export type ValueFormatOptions = {
unit?: string;
valueMaxFractioDigits?: number;
};

View File

@ -3,19 +3,17 @@ import { formatValue } from "./formatValue";
describe("formatValue", () => {
test("format with default options", () => {
expect(formatValue(42.42, { notionPropName: "whatever" })).toBe("42,4");
expect(formatValue(42, { notionPropName: "whatever" })).toBe("42");
expect(formatValue(42.42, {})).toBe("42,4");
expect(formatValue(42, {})).toBe("42");
});
test("format with valueMaxFractioDigits: 2", () => {
expect(
formatValue(42.4242, {
notionPropName: "whatever",
valueMaxFractioDigits: 2,
})
).toBe("42,42");
expect(
formatValue(42, {
notionPropName: "whatever",
valueMaxFractioDigits: 2,
})
).toBe("42");
@ -24,7 +22,6 @@ describe("formatValue", () => {
test("format with unit", () => {
expect(
formatValue(42, {
notionPropName: "whatever",
unit: "%",
})
).toBe("42%");

View File

@ -1,6 +1,6 @@
import { StatPublishOptions } from "../../statPublishOptions";
import { ValueFormatOptions } from "./ValueFormatOptions";
export function formatValue(value: number, publishOptions: StatPublishOptions) {
export function formatValue(value: number, publishOptions: ValueFormatOptions) {
const valueStr = value.toLocaleString("fr-FR", {
useGrouping: false,
maximumFractionDigits:

View File

@ -1,5 +1,5 @@
import { ValueWithEvol } from "../../../statistiques/ELStats";
import { StatPublishOptions } from "../../statPublishOptions";
import { StatPublishOptions } from "../notion/publish/v1/statPublishOptions";
import { ValueWithEvol } from "../statistiques/v1/ELStats";
import { formatValue } from "./formatValue";
export function formatValueWithEvol(

View File

@ -2,9 +2,12 @@ import { Client } from "@notionhq/client";
import { writeFileSync } from "fs";
import { checkDataConsistency } from "./data/checkDataConsistency";
import { fetchFamiliesWithEventsFromNotion } from "./notion/fetch/fetchFamiliesWithEventsFromNotion";
import { publishStatisticsToNotion } from "./notion/publish/publishStatisticsToNotion";
import { computeELStats } from "./statistiques/computeELStats";
import { computeStatsParAnciennete } from "./statistiques/computeEvenementsParAnciennete";
import { publishStatisticsToNotion } from "./notion/publish/v1/publishStatisticsToNotion";
import { publishStatsToPage } from "./notion/publish/v2/publishStatsToPage";
import { computeELStats } from "./statistiques/v1/computeELStats";
import { computeStatsParAnciennete } from "./statistiques/v1/computeEvenementsParAnciennete";
import { computeStatsPenales } from "./statistiques/v2/penales/computeStatsPenales";
import { statsPenalesDesc } from "./statistiques/v2/penales/StatsPenales";
type ProcessOptions = {
dryRun: boolean;
@ -65,13 +68,26 @@ function buildProcessOptions(): ProcessOptions {
"./el-stats-par-anciennete.json",
JSON.stringify(statsParAnciennete, null, " ")
);
const statsV2 = computeStatsPenales(familles);
if (options.dryRun) {
console.log(
"Dry run => Skip Publishing. Stats are dumped in file el-stats.json"
"Dry run => Skip Publishing. Stats are dumped in file el-stats-xxx.json"
);
writeFileSync("./el-stats.json", JSON.stringify(elStats, null, " "));
writeFileSync("./el-stats-v1.json", JSON.stringify(elStats, null, " "));
writeFileSync("./el-stats-v2.json", JSON.stringify(statsV2, null, " "));
} else {
console.log("Publishing statistics...");
publishStatisticsToNotion(elStats, currentDate, notionClient);
await publishStatisticsToNotion(elStats, currentDate, notionClient);
await publishStatsToPage(
notionClient,
"969eac5c-a4eb-49d4-b4ad-c341c9c4c785",
statsPenalesDesc,
statsV2
);
}
})();

View File

@ -22,6 +22,11 @@ export async function fetchFamiliesWithEventsFromNotion(
const eventPages = (
await queryAllDbResults(notionClient, {
database_id: familEventsDbId,
sorts: [
{property: "Date",
direction: "ascending"
}
]
})
).filter(isFullPage);
const familyPages = (

View File

@ -3,17 +3,17 @@ import {
PageObjectResponse,
UpdateDatabaseParameters,
} from "@notionhq/client/build/src/api-endpoints";
import { formatValueWithEvol } from "../../../format/formatValueWithEvol";
import {
ELStatsPeriod,
PeriodStatsValues,
ValueWithEvol,
} from "../../statistiques/ELStats";
import { StatPublishOptions, statPublishOptions } from "../statPublishOptions";
import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
import { queryAllDbResults } from "../utils/queryAllDbResults";
import { removeBlocks } from "../utils/removeBlocks";
import { CreatePageProperties } from "../utils/types/CreatePageProperties";
import { formatValueWithEvol } from "./format/formatValueWithEvol";
} from "../../../statistiques/v1/ELStats";
import { titlePropertyToText } from "../../utils/properties/titlePropertyToText";
import { queryAllDbResults } from "../../utils/queryAllDbResults";
import { removeBlocks } from "../../utils/removeBlocks";
import { CreatePageProperties } from "../../utils/types/CreatePageProperties";
import { StatPublishOptions, statPublishOptions } from "./statPublishOptions";
const periodeDbPropertyName = "Période";

View File

@ -1,7 +1,7 @@
import { Client, isFullBlock } from "@notionhq/client";
import { ELStats } from "../../statistiques/ELStats";
import { listAllChildrenBlocks } from "../utils/listAllChildrenBlocks";
import { richTextToPlainText } from "../utils/text/richTextToPlainText";
import { ELStats } from "../../../statistiques/v1/ELStats";
import { listAllChildrenBlocks } from "../../utils/listAllChildrenBlocks";
import { richTextToPlainText } from "../../utils/text/richTextToPlainText";
import { publishPeriodStats } from "./publishPeriodStats";
import { publishStatsActuelles } from "./publishStatsActuelles";

View File

@ -1,12 +1,12 @@
import { Client, isFullBlock } from "@notionhq/client";
import { BlockObjectRequest } from "@notionhq/client/build/src/api-endpoints";
import { ELStatsAtDate } from "../../statistiques/ELStats";
import { StatPublishOptions, statPublishOptions } from "../statPublishOptions";
import { listAllChildrenBlocks } from "../utils/listAllChildrenBlocks";
import { removeBlocks } from "../utils/removeBlocks";
import { richTextToPlainText } from "../utils/text/richTextToPlainText";
import { formatValue } from "./format/formatValue";
import { formatValue } from "../../../format/formatValue";
import { ELStatsAtDate } from "../../../statistiques/v1/ELStats";
import { listAllChildrenBlocks } from "../../utils/listAllChildrenBlocks";
import { removeBlocks } from "../../utils/removeBlocks";
import { richTextToPlainText } from "../../utils/text/richTextToPlainText";
import { currentStatsHeading, statsPageId } from "./publishStatisticsToNotion";
import { StatPublishOptions, statPublishOptions } from "./statPublishOptions";
export async function publishStatsActuelles(
notionClient: Client,

View File

@ -1,4 +1,6 @@
import { AllStatsPropNames } from "../statistiques/ELStats";
import { EvolFormatOptions } from "../../../format/EvolFormatOptions";
import { ValueFormatOptions } from "../../../format/ValueFormatOptions";
import { AllStatsPropNames } from "../../../statistiques/v1/ELStats";
export function statPublishOptions(
statJsPropName: AllStatsPropNames
@ -185,8 +187,5 @@ const statPropsPublishOptions: {
};
export type StatPublishOptions = {
notionPropName: string;
unit?: string;
valueMaxFractioDigits?: number;
evolMaxFractioDigits?: number;
evolPctMaxFractioDigits?: number;
};
} & ValueFormatOptions &
EvolFormatOptions;

View File

@ -0,0 +1,97 @@
import { Client, isFullBlock } from "@notionhq/client";
import { BlockObjectRequest } from "@notionhq/client/build/src/api-endpoints";
import { formatValue } from "../../../format/formatValue";
import {
isStatGroupDesc,
StatDesc,
StatGroupDesc,
StatsType,
} from "../../../statistiques/v2/StatsDesc";
import { listAllChildrenBlocks } from "../../utils/listAllChildrenBlocks";
import { removeBlocks } from "../../utils/removeBlocks";
export async function publishStatsToPage<D extends StatGroupDesc>(
notionClient: Client,
statsPageId: string,
descriptor: D,
stats: StatsType<D>
) {
const childrenBlocks = (
await listAllChildrenBlocks(notionClient, {
block_id: statsPageId,
})
).filter(isFullBlock);
const blocksIdsToRemove = childrenBlocks.map((b) => b.id);
await removeBlocks(notionClient, blocksIdsToRemove);
const newBlocks = createStatGroupChildrenListItemBlock(descriptor, stats);
await notionClient.blocks.children.append({
block_id: statsPageId,
children: [...newBlocks],
});
}
function createStatGroupListItemBlock<D extends StatGroupDesc>(
descriptor: D,
stats: StatsType<D>
): BulletedListItemBlockObjectRequest {
return {
bulleted_list_item: {
rich_text: [
{
text: {
content: descriptor.label,
},
},
],
children: createStatGroupChildrenListItemBlock(
descriptor,
stats
) as BulletedListItemChildren,
},
};
}
type BulletedListItemBlockObjectRequest = Extract<
BlockObjectRequest,
{ bulleted_list_item: object }
>;
type BulletedListItemChildren =
BulletedListItemBlockObjectRequest["bulleted_list_item"]["children"];
function createStatGroupChildrenListItemBlock<D extends StatGroupDesc>(
descriptor: D,
stats: StatsType<D>
): BulletedListItemBlockObjectRequest[] {
return Object.keys(descriptor.stats).map((statName) => {
const childStatDesc = descriptor.stats[statName];
const childStatValue = stats[statName];
return isStatGroupDesc(childStatDesc)
? createStatGroupListItemBlock(
childStatDesc,
childStatValue as StatsType<typeof childStatDesc>
)
: createStatListItemBlock(childStatDesc, childStatValue as number);
});
}
function createStatListItemBlock(
descriptor: StatDesc,
statValue: number
): BulletedListItemBlockObjectRequest {
return {
bulleted_list_item: {
rich_text: [
{
text: {
content:
descriptor.label + ": " + formatValue(statValue, descriptor),
},
},
],
},
};
}

View File

View File

@ -1,6 +1,6 @@
import { Famille } from "../data/Famille";
import { IdentifiedPeriod } from "../period/IdentifiedPeriod";
import { Period } from "../period/Period";
import { Famille } from "../../data/Famille";
import { IdentifiedPeriod } from "../../period/IdentifiedPeriod";
import { Period } from "../../period/Period";
import {
ELStatsOverPeriod,
ELStatsPeriod,

View File

@ -1,7 +1,7 @@
import { Famille } from "../data/Famille";
import { ELStats } from "./ELStats";
import { Famille } from "../../data/Famille";
import { computeELPeriodStats } from "./computeELPeriodStats";
import { computeELStatsAtDate } from "./computeELStatsAtDate";
import { ELStats } from "./ELStats";
import { generateELMonths } from "./generateELMonths";
import { generateELYears } from "./generateELYears";

View File

@ -4,17 +4,17 @@ import {
isEvenementBefore,
isProcedureCivile,
isProcedurePenale,
} from "../data/EvenementFamille";
} from "../../data/EvenementFamille";
import {
Famille,
dureeResistanceInDays,
isExResistant,
isResistant,
} from "../data/Famille";
import { average } from "../utils/math/average";
import { median } from "../utils/math/median";
import { percent } from "../utils/math/percent";
import { notNull } from "../utils/notNull";
} from "../../data/Famille";
import { average } from "../../utils/math/average";
import { median } from "../../utils/math/median";
import { percent } from "../../utils/math/percent";
import { notNull } from "../../utils/notNull";
import { ELStatsAtDate } from "./ELStats";
import { computePourcentageEntreeApresMiseEnDemeure } from "./computePourcentageEntreeApresMiseEnDemeure";

View File

@ -1,14 +1,14 @@
import {
isEvenementInPeriod,
isProcedurePenale,
} from "../data/EvenementFamille";
} from "../../data/EvenementFamille";
import {
Famille,
isResistantOverPeriod,
periodOfResistance,
} from "../data/Famille";
import { Period } from "../period/Period";
import { isPeriodContaining } from "../period/isPeriodContaining";
} from "../../data/Famille";
import { Period } from "../../period/Period";
import { isPeriodContaining } from "../../period/isPeriodContaining";
import { ELStatsOverPeriod } from "./ELStats";
import { computePourcentageEntreeApresMiseEnDemeure } from "./computePourcentageEntreeApresMiseEnDemeure";

View File

@ -1,5 +1,8 @@
import { isProcedureCivile, isProcedurePenale } from "../data/EvenementFamille";
import { Famille } from "../data/Famille";
import {
isProcedureCivile,
isProcedurePenale,
} from "../../data/EvenementFamille";
import { Famille } from "../../data/Famille";
import { computeFamillesWithEventsConditionInEarlyPeriod } from "./computeFamilleWithEventAfterDurationOfDC";
export function computeStatsParAnciennete(familles: Famille[]) {

View File

@ -1,7 +1,10 @@
import { addMonths } from "date-fns";
import { EvenementFamille, isEvenementBefore } from "../data/EvenementFamille";
import { Famille } from "../data/Famille";
import { percent } from "../utils/math/percent";
import {
EvenementFamille,
isEvenementBefore,
} from "../../data/EvenementFamille";
import { Famille } from "../../data/Famille";
import { percent } from "../../utils/math/percent";
type FamillesWithEventsConditionInEarlyPeriod = {
[name: string]: {

View File

@ -1,5 +1,5 @@
import { Famille } from "../data/Famille";
import { percent } from "../utils/math/percent";
import { Famille } from "../../data/Famille";
import { percent } from "../../utils/math/percent";
export function computePourcentageEntreeApresMiseEnDemeure(
familles: Famille[]

View File

@ -1,6 +1,6 @@
import { getMonth, getYear, setMonth, setYear } from "date-fns";
import { IdentifiedPeriod } from "../period/IdentifiedPeriod";
import { generateConsecutiveIdentifiedPeriods } from "../period/generateConsecutiveIdentifiedPeriods";
import { IdentifiedPeriod } from "../../period/IdentifiedPeriod";
import { generateConsecutiveIdentifiedPeriods } from "../../period/generateConsecutiveIdentifiedPeriods";
export function generateELMonths(): IdentifiedPeriod[] {
const months = generateConsecutiveIdentifiedPeriods({

View File

@ -1,6 +1,6 @@
import { addYears } from "date-fns/fp";
import { IdentifiedPeriod } from "../period/IdentifiedPeriod";
import { generateConsecutiveIdentifiedPeriods } from "../period/generateConsecutiveIdentifiedPeriods";
import { IdentifiedPeriod } from "../../period/IdentifiedPeriod";
import { generateConsecutiveIdentifiedPeriods } from "../../period/generateConsecutiveIdentifiedPeriods";
export function generateELYears(): IdentifiedPeriod[] {
return generateConsecutiveIdentifiedPeriods({

View File

@ -0,0 +1,11 @@
import { statsPenalesDesc } from "./penales/StatsPenales";
import { StatsType } from "./StatsDesc";
export const eLStatsV2Desc = {
label: "Stats v2",
stats: {
penales: statsPenalesDesc,
},
};
export type ELStatsV2 = StatsType<typeof eLStatsV2Desc>;

View File

@ -0,0 +1,32 @@
import { ValueFormatOptions } from "../../format/ValueFormatOptions";
export type StatGroupDesc = {
label: string;
desc?: string;
stats: { [propName: string]: StatDesc | StatGroupDesc };
};
export type StatDesc = {
label: string;
} & ValueFormatOptions;
export function isStatGroupDesc(x: unknown): x is StatGroupDesc {
if (typeof x !== "object" || Array.isArray(x) || x === null) {
return false;
}
if (!("label" in x) || x.label === null || typeof x.label !== "string") {
return false;
}
if (!("stats" in x) || x.stats === null || typeof x.stats !== "object") {
return false;
}
return true;
}
export type StatsType<T extends StatGroupDesc> = {
[Property in keyof T["stats"]]: T["stats"][Property] extends StatGroupDesc
? StatsType<T["stats"][Property]>
: number;
};

View File

@ -0,0 +1,11 @@
import { EvenementFamille } from "../../data/EvenementFamille";
import { Famille } from "../../data/Famille";
export function filterFamillesWithOneOfEvenements(
familles: Famille[],
evenementtPredicated: (evt: EvenementFamille) => boolean
): Famille[] {
return familles.filter(
(f) => f.Evenements.find((e) => evenementtPredicated(e)) !== undefined
);
}

View File

@ -0,0 +1,13 @@
import { Famille } from "../../data/Famille";
import { TypeEvenement } from "../../data/TypeEvenement";
import { filterFamillesWithOneOfEvenements } from "./filterFamillesWithOneOfEvenements";
export function filterFamillesWithOneOfEvenementsOfType(
familles: Famille[],
eventType: TypeEvenement
): Famille[] {
return filterFamillesWithOneOfEvenements(
familles,
(e) => e.Type === eventType
);
}

View File

@ -0,0 +1,66 @@
import { StatsType } from "../StatsDesc";
export const statsPenalesDesc = {
label: "Stats Pénales",
stats: {
nbFamillesMisesEnDemeure: {
label: "Nb familles mises en demeure",
},
nbFamillesAvecGendarmerie: {
label: "Nb familles avec un evenement lié à la Gendarmerie",
},
compositionPenales: {
label: "Compositions Pénales",
stats: {
nbFamilles: {
label: "Nb familles concernées",
},
acceptees: {
label: "Nb familles ayant acceptées",
},
refusees: {
label: "Nb familles ayant refusées",
},
},
},
crpc: {
label: "CRPC",
stats: {
nbFamilles: {
label: "Nb familles concernées",
},
acceptees: {
label: "Nb familles ayant acceptées",
},
refusees: {
label: "Nb familles ayant refusées",
},
},
},
tribunalCorrectionnel: {
label: "Tribunal Correctionnel",
stats: {
nbFamillesPassees: {
label: "Nb familles passées",
},
nbFamillesProgrammees: {
label: "Nb familles programmées",
},
nbFamillesRecidive: {
label: "Nb familles recidive",
},
},
},
intervalGendarmerieProcureur: {
label: "Délai moyen entre Gendarmerie et Procureur",
unit: " jours",
},
intervalProcureurTribunalCorrectionnel: {
label: "Délai moyen entre Procureur et Tribunal Correctionnel",
unit: " jours",
},
},
} as const;
export type StatsPenales = StatsType<typeof statsPenalesDesc>;

View File

@ -0,0 +1,159 @@
import { differenceInDays } from "date-fns";
import {
isCompositionPenale,
isCRPC,
isEvenementBefore,
isEvtProcureur,
isGendarmerie,
} from "../../../data/EvenementFamille";
import { Famille } from "../../../data/Famille";
import { average } from "../../../utils/math/average";
import { filterFamillesWithOneOfEvenements } from "../filterFamillesWithOneOfEvenements";
import { filterFamillesWithOneOfEvenementsOfType } from "../filterFamillesWithOneOfEvenementsOfType";
import { StatsPenales } from "./StatsPenales";
export function computeStatsPenales(familles: Famille[]): StatsPenales {
const famillesMisesEnDemeure = filterFamillesWithOneOfEvenementsOfType(
familles,
"Mise en demeure de scolarisation"
);
const famillesAvecGendarmerie = filterFamillesWithOneOfEvenements(
familles,
isGendarmerie
);
const statsPenales: StatsPenales = {
nbFamillesMisesEnDemeure: famillesMisesEnDemeure.length,
nbFamillesAvecGendarmerie: famillesAvecGendarmerie.length,
compositionPenales: computeCompositionPenales(familles),
crpc: computeCrpc(familles),
tribunalCorrectionnel: computeTribunalCorrectionnel(familles),
intervalGendarmerieProcureur: computeIntervalGendarmerieProcureur(familles),
intervalProcureurTribunalCorrectionnel:
computeIntervalProcureurTribunalCorrectionnel(familles),
};
return statsPenales;
}
function computeCrpc(familles: Famille[]): StatsPenales["crpc"] {
const famillesConcernees = filterFamillesWithOneOfEvenements(familles, (e) =>
isCRPC(e)
);
const acceptees = filterFamillesWithOneOfEvenementsOfType(
familles,
"Acceptation CRPC"
);
const refusees = filterFamillesWithOneOfEvenementsOfType(
familles,
"Refus CRPC"
);
return {
nbFamilles: famillesConcernees.length,
acceptees: acceptees.length,
refusees: refusees.length,
};
}
function computeCompositionPenales(
familles: Famille[]
): StatsPenales["compositionPenales"] {
const famillesConcernees = filterFamillesWithOneOfEvenements(familles, (e) =>
isCompositionPenale(e)
);
const acceptees = filterFamillesWithOneOfEvenementsOfType(
familles,
"Composition pénale acceptée"
);
const refusees = filterFamillesWithOneOfEvenementsOfType(
familles,
"Composition pénale refusée"
);
return {
nbFamilles: famillesConcernees.length,
acceptees: acceptees.length,
refusees: refusees.length,
};
}
function computeTribunalCorrectionnel(
familles: Famille[]
): StatsPenales["tribunalCorrectionnel"] {
const now = new Date();
const famillesPassees = filterFamillesWithOneOfEvenements(
familles,
(e) => e.Type === "Tribunal correctionnel" && isEvenementBefore(e, now)
);
const famillesProgrammees = filterFamillesWithOneOfEvenements(
familles,
(e) => e.Type === "Tribunal correctionnel" && !isEvenementBefore(e, now)
);
const famillesRecidiveTribunal = familles.filter((f) => {
return (
f.Evenements.filter((e) => e.Type === "Tribunal correctionnel").length > 1
);
});
return {
nbFamillesPassees: famillesPassees.length,
nbFamillesProgrammees: famillesProgrammees.length,
nbFamillesRecidive: famillesRecidiveTribunal.length,
};
}
function computeIntervalGendarmerieProcureur(familles: Famille[]): number {
const intervals = familles.flatMap((f) => {
const evtGendarmerie = f.Evenements.find((e) => isGendarmerie(e));
const evtProcureur = f.Evenements.find((e) => isEvtProcureur(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 for ${f.Titre} (${f.notionId})`
);
return [];
} else {
return [intervalInDays];
}
});
return average(intervals);
}
function computeIntervalProcureurTribunalCorrectionnel(
familles: Famille[]
): number {
const intervals = familles.flatMap((f) => {
const evtProcureur = f.Evenements.find((e) => isEvtProcureur(e));
const evtTribunal = f.Evenements.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);
}