refactor: garde tous les evenements dans des propriétés dédiés

* garde tous les evenements dans des propriétés dédiés
* cleanup process de préparation des donées
wip-related-pages
Sébastien Arod 2024-12-16 12:51:50 +01:00
parent 9839a26ef0
commit 88cacc60d6
24 changed files with 180 additions and 267 deletions

View File

@ -22,8 +22,17 @@ export type Famille = Readonly<{
Penal: StatutPenal;
Social: StatutSocial;
Departement: string | null;
// sorted by date asc
// Tous Evenements triés par date asc
// Inclus aussi les evenements sans date
Evenements: EvenementFamille[];
// Evenements durant la période EL triés par date asc
EvenementsEL: EvenementFamille[];
// Evenements hors période EL triés par date asc
EvenementsAvantEL: EvenementFamille[];
EvenementsApresEL: EvenementFamille[];
DerniereModification: Date;
}>;

View File

@ -1,28 +0,0 @@
import { EvenementFamille } from "../../EvenementFamille";
import { Famille } from "../../Famille";
import {
MessageDeNettoyage,
msgDonneeIgnoree,
} from "../fwk/MessageDeNettoyage";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
export function ignorerEvenements(
familles: Famille[],
conditionAIgnoree: (evt: EvenementFamille, f: Famille) => boolean,
messageIgnoreeFn: (evt: EvenementFamille, f: Famille) => string
): DonneesNettoyees {
let messages: MessageDeNettoyage[] = [];
const nettoyees = familles.map((f) => {
const nettoyees = f.Evenements.filter((e) => !conditionAIgnoree(e, f));
const evtIgnorees = f.Evenements.filter((e) => conditionAIgnoree(e, f));
messages = [
...messages,
...evtIgnorees.map((e) => msgDonneeIgnoree(messageIgnoreeFn(e, f))),
];
return { ...f, Evenements: nettoyees };
});
return {
familles: nettoyees,
messages,
};
}

View File

@ -1,21 +0,0 @@
import { EvenementFamille } from "../../EvenementFamille";
import { Famille } from "../../Famille";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import { ignorerEvenements } from "./ignorerEvenements";
export function supprimerLesEvenementsHorsResisstance(
familles: Famille[]
): DonneesNettoyees {
return ignorerEvenements(
familles,
(e: EvenementFamille, f: Famille) => {
return (
e.Date !== null &&
((f.Integration !== null && e.Date < f.Integration) ||
(f.Sortie !== null && e.Date > f.Sortie))
);
},
(e, f) =>
`Evenement "${f.Titre} - ${e.Évènement}" [${e.notionId}] hors période de résistance il sera ignoré`
);
}

View File

@ -1,15 +0,0 @@
import { EvenementFamille } from "../../EvenementFamille";
import { Famille } from "../../Famille";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import { ignorerEvenements } from "./ignorerEvenements";
export function supprimerLesEvenementsSansDate(
familles: Famille[]
): DonneesNettoyees {
return ignorerEvenements(
familles,
(e: EvenementFamille) => e.Date === null,
(e, f) =>
`Evenement "${f.Titre} - ${e.Évènement}" [${e.notionId}] n'as pas de Date il sera ignoré`
);
}

View File

@ -1,64 +0,0 @@
import { Famille, isExResistant, isResistant } from "../../Famille";
import { statutExResistant, statutResistant } from "../../StatutFamille";
export function checkDataConsistency(families: Famille[]): ConsistencyReport {
const reports = families.map((family) => {
return checkFamilyDataConsistency(family);
});
return {
errors: reports.flatMap((r) => r.errors),
warnings: reports.flatMap((r) => r.warnings),
};
}
export type ConsistencyReport = {
warnings: ConsistencyIssue[];
errors: ConsistencyIssue[];
};
export type ConsistencyIssue = {
issueType: string;
familyId: string;
};
function checkFamilyDataConsistency(family: Famille): ConsistencyReport {
const consistencyErrors: ConsistencyIssue[] = [];
const consistencyWarnings: ConsistencyIssue[] = [];
if (family.Statut === statutExResistant) {
if (
family.Integration &&
family.Sortie &&
family.Integration > family.Sortie
) {
consistencyErrors.push({
familyId: family.Titre,
issueType: "Date Intégration > date Sortie ",
});
}
}
if (
(isResistant(family) || isExResistant(family)) &&
family.Integration !== null
) {
const miseEnDemeureBeforeInteg =
family.Evenements.find(
(e) =>
e.Type === "Mise en demeure de scolarisation" &&
(e.Date === null || e.Date < family.Integration!)
) !== undefined;
if (
miseEnDemeureBeforeInteg &&
family.ContexteEntree !== "Après mise en demeure" &&
family.ContexteEntree !== "Après poursuite procureur"
) {
consistencyWarnings.push({
familyId: family.Titre,
issueType: `Valeur de ContextEntree incorrecte: Le Context d'Entree est "${family.ContexteEntree}" alors que la date de mise en demeure avant date d'intégration`,
});
}
}
return {
errors: consistencyErrors,
warnings: consistencyWarnings,
};
}

View File

@ -1,16 +1,19 @@
import { Famille } from "../../Famille";
import { integrationAFinaliser, statutExResistant } from "../../StatutFamille";
import { corrigerListeFamilles } from "./corrigerListeFamilles";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import { nettoyerFamilles } from "./nettoyerFamilles";
import { msgDonneeCorrigee } from "../fwk/MessageDeNettoyage";
export function corrigerFamilleExResistanteSansDateIntegration(
familles: Famille[]
): DonneesNettoyees {
return corrigerListeFamilles(
return nettoyerFamilles(
familles,
(f) => f.Statut === statutExResistant && f.Integration === null,
(f) => ({ ...f, Statut: integrationAFinaliser }),
(f) =>
msgDonneeCorrigee(
`Famille ${f.Titre} - avec un statut Ex Résistant maos sans date d'intégration => Le statut est remplacé par "${integrationAFinaliser}" pour le calcul des statistics`
)
);
}

View File

@ -0,0 +1,19 @@
import { Famille } from "../../Famille";
import { statutExResistant, statutResistant } from "../../StatutFamille";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import { msgDonneeCorrigee } from "../fwk/MessageDeNettoyage";
import { nettoyerFamilles } from "./nettoyerFamilles";
export function corrigerFamilleExResistanteSansDateSortie(
familles: Famille[]
): DonneesNettoyees {
return nettoyerFamilles(
familles,
(f) => f.Statut === statutExResistant && f.Sortie === null,
(f) => ({ ...f, Statut: statutResistant }),
(f) =>
msgDonneeCorrigee(
`Famille ${f.Titre} - avec un statut Ex Résistant maos sans date de Sortie => Le statut est remplacé par "${statutResistant}" pour le calcul des statistics`
)
);
}

View File

@ -1,20 +0,0 @@
import { Famille } from "../../Famille";
import {
integrationAFinaliser,
statutExResistant,
statutResistant,
} from "../../StatutFamille";
import { corrigerListeFamilles } from "./corrigerListeFamilles";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
export function corrigerFamilleExResistanteSansDateSortie(
familles: Famille[]
): DonneesNettoyees {
return corrigerListeFamilles(
familles,
(f) => f.Statut === statutExResistant && f.Sortie === null,
(f) => ({ ...f, Statut: statutResistant }),
(f) =>
`Famille ${f.Titre} - avec un statut Ex Résistant maos sans date de Sortie => Le statut est remplacé par "${statutResistant}" pour le calcul des statistics`
);
}

View File

@ -1,16 +1,19 @@
import { Famille } from "../../Famille";
import { statutResistant } from "../../StatutFamille";
import { corrigerListeFamilles } from "./corrigerListeFamilles";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import { msgDonneeCorrigee } from "../fwk/MessageDeNettoyage";
import { nettoyerFamilles } from "./nettoyerFamilles";
export function corrigerFamilleResistanteAvecDateSortie(
familles: Famille[]
): DonneesNettoyees {
return corrigerListeFamilles(
return nettoyerFamilles(
familles,
(f) => f.Statut === statutResistant && f.Sortie !== null,
(f) => ({ ...f, Sortie: null }),
(f) =>
msgDonneeCorrigee(
`Famille ${f.Titre} - avec un statut Résistant mais avec une date de Sortie => La date de Sortie est mise a null pour le calcul des statistics`
)
);
}

View File

@ -1,16 +1,19 @@
import { Famille } from "../../Famille";
import { integrationAFinaliser, statutResistant } from "../../StatutFamille";
import { corrigerListeFamilles } from "./corrigerListeFamilles";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import { msgDonneeCorrigee } from "../fwk/MessageDeNettoyage";
import { nettoyerFamilles } from "./nettoyerFamilles";
export function corrigerFamilleResistanteSansDateIntegration(
familles: Famille[]
): DonneesNettoyees {
return corrigerListeFamilles(
return nettoyerFamilles(
familles,
(f) => f.Statut === statutResistant && f.Integration === null,
(f) => ({ ...f, Statut: integrationAFinaliser }),
(f) =>
msgDonneeCorrigee(
`Famille ${f.Titre} - avec un statut Résistant maos sans date d'intégration => Le statut est remplacé par "${integrationAFinaliser}" pour le calcul des statistics`
)
);
}

View File

@ -1,28 +0,0 @@
import { Famille } from "../../Famille";
import {
MessageDeNettoyage,
msgDonneeCorrigee,
} from "../fwk/MessageDeNettoyage";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
export function corrigerListeFamilles(
familles: Famille[],
conditionACorriger: (f: Famille) => boolean,
fonctionDeCorrection: (f: Famille) => Famille,
messageDescriptifCorrection: (f: Famille) => string
): DonneesNettoyees {
const messages: MessageDeNettoyage[] = [];
const famillesNettoyees = familles.map((f) => {
if (conditionACorriger(f)) {
messages.push(msgDonneeCorrigee(messageDescriptifCorrection(f)));
return fonctionDeCorrection(f);
} else {
return f;
}
});
return {
familles: famillesNettoyees,
messages,
};
}

View File

@ -0,0 +1,23 @@
import { Famille } from "../../Famille";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import { msgDonneeSuspecte } from "../fwk/MessageDeNettoyage";
import { nettoyerFamilles } from "./nettoyerFamilles";
export function detecterLesContextesDEntreeIncoherents(
familles: Famille[]
): DonneesNettoyees {
return nettoyerFamilles(
familles,
(f) =>
!!f.EvenementsAvantEL.find(
(e) => e.Type === "Mise en demeure de scolarisation"
) &&
f.ContexteEntree !== "Après mise en demeure" &&
f.ContexteEntree !== "Après poursuite procureur",
(f) => f,
(f) =>
msgDonneeSuspecte(
`${f.Titre} - ContextEntree incohérent: Mise en demeure avant date d'intégration alors que le Contexte d'Entree est "${f.ContexteEntree}" `
)
);
}

View File

@ -0,0 +1,37 @@
import { Famille } from "../../Famille";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import { MessageDeNettoyage } from "../fwk/MessageDeNettoyage";
export function initialiserLesEvenementsDetailles(
familles: Famille[]
): DonneesNettoyees {
const messages: MessageDeNettoyage[] = [];
const nettoyees = familles.map((f) => {
const EvenementsAvantEL = f.Evenements.filter(
(e) => e.Date !== null && f.Integration !== null && e.Date < f.Integration
);
const EvenementsEL = f.Evenements.filter(
(e) =>
e.Date !== null &&
f.Integration !== null &&
e.Date >= f.Integration &&
(f.Sortie === null || e.Date <= f.Sortie)
);
const EvenementsApresEL = f.Evenements.filter(
(e) => e.Date !== null && f.Sortie !== null && e.Date > f.Sortie
);
return {
...f,
EvenementsAvantEL: EvenementsAvantEL,
EvenementsEL: EvenementsEL,
EvenementsApresEL: EvenementsApresEL,
};
});
return {
familles: nettoyees,
messages,
};
}

View File

@ -1,61 +0,0 @@
import { Famille } from "../../Famille";
import { appliquerLesFonctionsDeNettoyages } from "../fwk/appliquerLesFonctionsDeNettoyages";
import { checkDataConsistency } from "./checkDataConsistency";
import { corrigerFamilleResistanteSansDateIntegration } from "./corrigerFamilleResistanteSansDateIntegration";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import {
MessageDeNettoyage,
msgDonneeBloquante,
msgDonneeSuspecte,
} from "../fwk/MessageDeNettoyage";
import { supprimerLesFamillesVide } from "./supprimerLesFamillesVide";
import { supprimerLesEvenementsHorsResisstance } from "../evt/supprimerLesEvenementsHorsResisstance";
import { supprimerLesEvenementsSansDate } from "../evt/supprimerLesEvenementsSansDate";
import { corrigerFamilleResistanteAvecDateSortie } from "./corrigerFamilleResistanteAvecDateSortie";
import { corrigerFamilleExResistanteSansDateIntegration } from "./corrigerFamilleExResistanteSansDateIntegration";
import { corrigerFamilleExResistanteSansDateSortie } from "./corrigerFamilleExResistanteSansDateSortie";
export function nettoyerDonneesFamilles(
donneesFamillesBrutes: Famille[]
): DonneesNettoyees {
const nettoyagePreConsistencyCheck = appliquerLesFonctionsDeNettoyages(
donneesFamillesBrutes,
[
supprimerLesFamillesVide,
corrigerFamilleResistanteSansDateIntegration,
corrigerFamilleResistanteAvecDateSortie,
corrigerFamilleExResistanteSansDateIntegration,
corrigerFamilleExResistanteSansDateSortie,
]
);
// TODO convert checkDataConsistency to filters
const consistencyReport = checkDataConsistency(
nettoyagePreConsistencyCheck.familles
);
// Adapte les message
const consistencyCheckErrorMessages: MessageDeNettoyage[] =
consistencyReport.errors.map((e) =>
msgDonneeBloquante(`${e.familyId} - ${e.issueType}`)
);
const consistencyCheckWarnings: MessageDeNettoyage[] =
consistencyReport.warnings.map((e) =>
msgDonneeSuspecte(`${e.familyId} - ${e.issueType}`)
);
const nettoyagePostConsistencyCheck = appliquerLesFonctionsDeNettoyages(
nettoyagePreConsistencyCheck.familles,
[supprimerLesEvenementsSansDate, supprimerLesEvenementsHorsResisstance]
);
return {
familles: nettoyagePostConsistencyCheck.familles,
messages: [
...nettoyagePreConsistencyCheck.messages,
...consistencyCheckErrorMessages,
...consistencyCheckWarnings,
...nettoyagePostConsistencyCheck.messages,
],
};
}

View File

@ -0,0 +1,25 @@
import { Famille } from "../../Famille";
import { MessageDeNettoyage } from "../fwk/MessageDeNettoyage";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
export function nettoyerFamilles(
familles: Famille[],
conditionANettoyer: (f: Famille) => boolean,
fonctionDeNettoyage: (f: Famille) => Famille,
messageNettoyage: (f: Famille) => MessageDeNettoyage
): DonneesNettoyees {
const messages: MessageDeNettoyage[] = [];
const famillesNettoyees = familles.map((f) => {
if (conditionANettoyer(f)) {
messages.push(messageNettoyage(f));
return fonctionDeNettoyage(f);
} else {
return f;
}
});
return {
familles: famillesNettoyees,
messages,
};
}

View File

@ -0,0 +1,29 @@
import { Famille } from "../../Famille";
import { appliquerLesFonctionsDeNettoyages } from "../fwk/appliquerLesFonctionsDeNettoyages";
import { corrigerFamilleResistanteSansDateIntegration } from "./corrigerFamilleResistanteSansDateIntegration";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import { supprimerLesFamillesVide } from "./supprimerLesFamillesVide";
import { corrigerFamilleResistanteAvecDateSortie } from "./corrigerFamilleResistanteAvecDateSortie";
import { corrigerFamilleExResistanteSansDateIntegration } from "./corrigerFamilleExResistanteSansDateIntegration";
import { initialiserLesEvenementsDetailles } from "./initialiserLesEvenementsDetailles";
import { corrigerFamilleExResistanteSansDateSortie } from "./corrigerFamilleExResistanteSansDateSortie copy";
import { detecterLesContextesDEntreeIncoherents } from "./detecterLesContextesDEntreeIncoherents";
export function nettoyerDonneesFamilles(
donneesFamillesBrutes: Famille[]
): DonneesNettoyees {
const nettoyagePreConsistencyCheck = appliquerLesFonctionsDeNettoyages(
donneesFamillesBrutes,
[
supprimerLesFamillesVide,
corrigerFamilleResistanteSansDateIntegration,
corrigerFamilleResistanteAvecDateSortie,
corrigerFamilleExResistanteSansDateIntegration,
corrigerFamilleExResistanteSansDateSortie,
initialiserLesEvenementsDetailles,
detecterLesContextesDEntreeIncoherents,
]
);
return nettoyagePreConsistencyCheck;
}

View File

@ -17,7 +17,7 @@ import { computeStatsGeneralesMensuelles } from "./statistiques/v2/generales/com
import { mermaidDiagramStatsGeneralesMensuelles } from "./statistiques/v2/generales/mermaidDiagramStatsGeneralesMensuelles";
import { publishStatsGenerales } from "./notion/publish/v2/publishStatsGenerales";
import { typeEvenementsProcedurePenale } from "./data/TypeEvenementsPenal";
import { nettoyerDonneesFamilles } from "./data/nettoyage/familles/nettoyerDonneesFamilles";
import { nettoyerDonneesFamilles } from "./data/nettoyage/familles/preparerDonneesFamilles";
type ProcessOptions = {
dryRun: boolean;

View File

@ -104,6 +104,10 @@ function buildFamily(
Integration: datePropertyToDate(pageProperties, "Intégration"),
Sortie: datePropertyToDate(pageProperties, "Sortie"),
Evenements: familyEvents.filter((fe) => fe.notionIdFamille === page.id),
// Ces trois propriétés seront peuplés après le data consistency check
EvenementsEL: [],
EvenementsAvantEL: [],
EvenementsApresEL: [],
Departement: departement?.name || null,
DerniereModification: datePropertyToDate(
pageProperties,

View File

@ -1,7 +1,3 @@
import {
isEvenementBefore,
isProcedureCivile,
} from "../../data/EvenementFamille";
import { Famille, isExResistant, isResistant } from "../../data/Famille";
import { percent } from "../../utils/math/percent";
import { ELStatsAtDate } from "./ELStats";
@ -16,11 +12,11 @@ export function computeELStatsAtDate(
);
const famillesAvecContrôleFiscal = familleResistantesOrEx.filter((f) =>
f.Evenements.find((e) => e.Type === "Contrôle fiscal")
f.EvenementsEL.find((e) => e.Type === "Contrôle fiscal")
);
const famillesAvecContrôleURSAFF = familleResistantesOrEx.filter((f) =>
f.Evenements.find((e) => e.Type === "Contrôle URSSAF")
f.EvenementsEL.find((e) => e.Type === "Contrôle URSSAF")
);
const elStats: ELStatsAtDate<number> = {
// Autre

View File

@ -45,7 +45,7 @@ export const computeFamillesWithEventsConditionInEarlyPeriod = (
(f) => {
const dcDate = dcStartDate(f)!;
const dcPeriodEnd = addMonths(dcDate, months);
const eventsBeforeDate = f.Evenements.filter((e) =>
const eventsBeforeDate = f.EvenementsEL.filter((e) =>
isEvenementBefore(e, dcPeriodEnd)
);
return eventsPredicate(eventsBeforeDate);

View File

@ -6,6 +6,6 @@ export function filterFamillesWithOneOfEvenements(
evenementtPredicated: (evt: EvenementFamille) => boolean
): Famille[] {
return familles.filter(
(f) => f.Evenements.find((e) => evenementtPredicated(e)) !== undefined
(f) => f.EvenementsEL.find((e) => evenementtPredicated(e)) !== undefined
);
}

View File

@ -17,7 +17,7 @@ export function computeStatsGenerales(familles: Famille[]): StatsGenerales {
const famillesResistantes = familles.filter((f) => isResistant(f));
const famillesSansEvenements = famillesResistantes.filter(
(f) => f.Evenements.length === 0
(f) => f.EvenementsEL.length === 0
);
const dureesResistances = famillesResistantesOrEx.map(
@ -35,8 +35,7 @@ export function computeStatsGenerales(familles: Famille[]): StatsGenerales {
).length,
dureeResistanceMedianne: median(dureesResistances),
dureeResistanceMoyenne: average(dureesResistances),
nbFamillesResistantesActuellesSansEvenements:
famillesSansEvenements.length,
nbFamillesResistantesActuellesSansEvenements: famillesSansEvenements.length,
nbFamillesParContexteDEntree: sortByKey(
countBy(famillesResistantesOrEx, (f) => f.ContexteEntree)
),

View File

@ -6,7 +6,7 @@ export function computeSequencEvtPenalSankeyData(familles: Famille[]) {
familles.forEach((f) => {
// Compute all transitions, Events are already sorted
const nonNullEvents = f.Evenements.filter(
const nonNullEvents = f.EvenementsEL.filter(
(evt) => evt.Type !== null && (evt.Type as string) !== "null"
);

View File

@ -36,7 +36,7 @@ export function computeStatsPenales(familles: Famille[]): StatsPenales {
const famillesAvecGendarmerieSansSuiteACeJour = famillesGendarmerie.filter(
(f) => {
const evtsProcPenaleHorsGendarmerie = f.Evenements.filter(
const evtsProcPenaleHorsGendarmerie = f.EvenementsEL.filter(
isProcedurePenaleHorsGendarmerie
);
return evtsProcPenaleHorsGendarmerie.length === 0;
@ -50,7 +50,7 @@ export function computeStatsPenales(familles: Famille[]): StatsPenales {
const famillesAvecInfoTribunaux: FamilleAvecInfoTribunaux[] =
famillesResistantesOuEx.map((f) => {
const evtTribunaux = f.Evenements.filter(isTribunalCorrectionnel);
const evtTribunaux = f.EvenementsEL.filter(isTribunalCorrectionnel);
return {
...f,
evtTribunal1: evtTribunaux.length > 0 ? evtTribunaux[0] : undefined,
@ -207,8 +207,8 @@ function computeTribunalCorrectionnel2(
function computeIntervalGendarmerieProcureur(familles: Famille[]): number {
const intervals = familles.flatMap((f) => {
const evtGendarmerie = f.Evenements.find((e) => isGendarmerie(e));
const evtProcureur = f.Evenements.find((e) => isProcureur(e));
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) {
@ -235,8 +235,8 @@ function computeIntervalProcureurTribunalCorrectionnel(
familles: Famille[]
): number {
const intervals = familles.flatMap((f) => {
const evtProcureur = f.Evenements.find((e) => isProcureur(e));
const evtTribunal = f.Evenements.find(
const evtProcureur = f.EvenementsEL.find((e) => isProcureur(e));
const evtTribunal = f.EvenementsEL.find(
(e) => e.Type === "Tribunal correctionnel"
);