feat: correction automatique des données

wip-related-pages
Sébastien Arod 2024-12-08 15:00:58 +01:00
parent e906093d1d
commit 1a67b7888f
20 changed files with 245 additions and 105 deletions

View File

@ -4,7 +4,11 @@ import { arePeriodsOverlaping } from "../period/arePeriodsOverlaping";
import { isPeriodContaining } from "../period/isPeriodContaining"; import { isPeriodContaining } from "../period/isPeriodContaining";
import { ContexteEntreeDC } from "./ContexteEntreeDC"; import { ContexteEntreeDC } from "./ContexteEntreeDC";
import { EvenementFamille } from "./EvenementFamille"; import { EvenementFamille } from "./EvenementFamille";
import { StatutFamille } from "./StatutFamille"; import {
statutExResistant,
StatutFamille,
statutResistant,
} from "./StatutFamille";
import { StatutSocial } from "./StatutSocial"; import { StatutSocial } from "./StatutSocial";
import { StatutPenal } from "./StatutPenal"; import { StatutPenal } from "./StatutPenal";
@ -27,7 +31,10 @@ export function periodOfResistance(
family: Famille, family: Famille,
atDate: Date = new Date(Date.now()) atDate: Date = new Date(Date.now())
): Period | null { ): Period | null {
if (family.Statut !== "Résistant·e" && family.Statut !== "Ex résistant·e") { if (
family.Statut !== statutResistant &&
family.Statut !== statutExResistant
) {
return null; return null;
} }
if (!family.Integration || family.Integration > atDate) { if (!family.Integration || family.Integration > atDate) {

View File

@ -1,15 +1,20 @@
export const integrationAFinaliser =
"Intégration à finaliser (vérification de la fiche)";
export const statutsIntegrationEnCours = [ export const statutsIntegrationEnCours = [
"Intégration à finaliser (vérification de la fiche)", integrationAFinaliser,
"Intégration en cours", "Intégration en cours",
] as const; ] as const;
export const statutsPreIntegration = ["en réflexion"] as const; export const statutsPreIntegration = ["en réflexion"] as const;
export const statutsIntegrationEnEchec = ["Abandon", "Incompatible"] as const; export const statutsIntegrationEnEchec = ["Abandon", "Incompatible"] as const;
export const statutResistant = "Résistant·e";
export const statutExResistant = "Ex résistant·e";
export const statutsFamille = [ export const statutsFamille = [
...statutsPreIntegration, ...statutsPreIntegration,
"Résistant·e", statutResistant,
"Ex résistant·e", statutExResistant,
...statutsIntegrationEnCours, ...statutsIntegrationEnCours,
...statutsIntegrationEnEchec, ...statutsIntegrationEnEchec,
] as const; ] as const;

View File

@ -1,7 +1,10 @@
import { EvenementFamille } from "../EvenementFamille"; import { EvenementFamille } from "../../EvenementFamille";
import { Famille } from "../Famille"; import { Famille } from "../../Famille";
import { MessageDeNettoyage, msgDonneeIgnoree } from "./MessageDeNettoyage"; import {
import { DonneesNettoyees } from "./nettoyerDonneesFamilles"; MessageDeNettoyage,
msgDonneeIgnoree,
} from "../fwk/MessageDeNettoyage";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
export function ignorerEvenements( export function ignorerEvenements(
familles: Famille[], familles: Famille[],

View File

@ -1,6 +1,6 @@
import { EvenementFamille } from "../EvenementFamille"; import { EvenementFamille } from "../../EvenementFamille";
import { Famille } from "../Famille"; import { Famille } from "../../Famille";
import { DonneesNettoyees } from "./nettoyerDonneesFamilles"; import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import { ignorerEvenements } from "./ignorerEvenements"; import { ignorerEvenements } from "./ignorerEvenements";
export function supprimerLesEvenementsHorsResisstance( export function supprimerLesEvenementsHorsResisstance(

View File

@ -1,6 +1,6 @@
import { EvenementFamille } from "../EvenementFamille"; import { EvenementFamille } from "../../EvenementFamille";
import { Famille } from "../Famille"; import { Famille } from "../../Famille";
import { DonneesNettoyees } from "./nettoyerDonneesFamilles"; import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
import { ignorerEvenements } from "./ignorerEvenements"; import { ignorerEvenements } from "./ignorerEvenements";
export function supprimerLesEvenementsSansDate( export function supprimerLesEvenementsSansDate(

View File

@ -1,4 +1,5 @@
import { Famille, isExResistant, isResistant } from "../Famille"; import { Famille, isExResistant, isResistant } from "../../Famille";
import { statutExResistant, statutResistant } from "../../StatutFamille";
export function checkDataConsistency(families: Famille[]): ConsistencyReport { export function checkDataConsistency(families: Famille[]): ConsistencyReport {
const reports = families.map((family) => { const reports = families.map((family) => {
@ -22,32 +23,7 @@ function checkFamilyDataConsistency(family: Famille): ConsistencyReport {
const consistencyErrors: ConsistencyIssue[] = []; const consistencyErrors: ConsistencyIssue[] = [];
const consistencyWarnings: ConsistencyIssue[] = []; const consistencyWarnings: ConsistencyIssue[] = [];
if (family.Statut === "Résistant·e") { if (family.Statut === statutExResistant) {
if (family.Integration === null) {
consistencyErrors.push({
familyId: family.Titre,
issueType: "Résistant·e sans date d'Intégration",
});
}
if (family.Sortie !== null) {
consistencyErrors.push({
familyId: family.Titre,
issueType: "Résistant·e avec Date de Sortie",
});
}
} else if (family.Statut === "Ex résistant·e") {
if (family.Integration === null) {
consistencyErrors.push({
familyId: family.Titre,
issueType: "Ex résistant·e sans date Intégration",
});
}
if (family.Sortie === null) {
consistencyErrors.push({
familyId: family.Titre,
issueType: "Ex résistant·e sans date Sortie",
});
}
if ( if (
family.Integration && family.Integration &&
family.Sortie && family.Sortie &&

View File

@ -0,0 +1,16 @@
import { Famille } from "../../Famille";
import { integrationAFinaliser, statutExResistant } from "../../StatutFamille";
import { corrigerListeFamilles } from "./corrigerListeFamilles";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
export function corrigerFamilleExResistanteSansDateIntegration(
familles: Famille[]
): DonneesNettoyees {
return corrigerListeFamilles(
familles,
(f) => f.Statut === statutExResistant && f.Integration === null,
(f) => ({ ...f, Statut: integrationAFinaliser }),
(f) =>
`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,20 @@
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

@ -0,0 +1,16 @@
import { Famille } from "../../Famille";
import { statutResistant } from "../../StatutFamille";
import { corrigerListeFamilles } from "./corrigerListeFamilles";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
export function corrigerFamilleResistanteAvecDateSortie(
familles: Famille[]
): DonneesNettoyees {
return corrigerListeFamilles(
familles,
(f) => f.Statut === statutResistant && f.Sortie !== null,
(f) => ({ ...f, Sortie: null }),
(f) =>
`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

@ -0,0 +1,16 @@
import { Famille } from "../../Famille";
import { integrationAFinaliser, statutResistant } from "../../StatutFamille";
import { corrigerListeFamilles } from "./corrigerListeFamilles";
import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
export function corrigerFamilleResistanteSansDateIntegration(
familles: Famille[]
): DonneesNettoyees {
return corrigerListeFamilles(
familles,
(f) => f.Statut === statutResistant && f.Integration === null,
(f) => ({ ...f, Statut: integrationAFinaliser }),
(f) =>
`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

@ -0,0 +1,28 @@
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,61 @@
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

@ -1,11 +1,13 @@
import { Famille } from "../Famille"; import { Famille } from "../../Famille";
import { msgDonneeIgnoree } from "./MessageDeNettoyage"; import { msgDonneeIgnoree } from "../fwk/MessageDeNettoyage";
import { DonneesNettoyees } from "./nettoyerDonneesFamilles"; import { DonneesNettoyees } from "../fwk/DonneesNettoyees";
/** /**
* Supprime "Famille de résistant" qui sont souvent créé par erreur * Supprime "Famille de résistant" qui sont souvent créé par erreur
*/ */
export function supprimerLesEntreesVide(familles: Famille[]): DonneesNettoyees { export function supprimerLesFamillesVide(
familles: Famille[]
): DonneesNettoyees {
const nettoyees = familles.filter((f) => f.Titre !== "Famille de résistant"); const nettoyees = familles.filter((f) => f.Titre !== "Famille de résistant");
const ignorees = familles.filter((f) => f.Titre === "Famille de résistant"); const ignorees = familles.filter((f) => f.Titre === "Famille de résistant");
if (ignorees.length > 0) { if (ignorees.length > 0) {

View File

@ -0,0 +1,7 @@
import { Famille } from "../../Famille";
import { MessageDeNettoyage } from "./MessageDeNettoyage";
export type DonneesNettoyees = {
familles: Famille[];
messages: MessageDeNettoyage[];
};

View File

@ -0,0 +1,4 @@
import { Famille } from "../../Famille";
import { DonneesNettoyees } from "./DonneesNettoyees";
export type FonctionNettoyage = (familles: Famille[]) => DonneesNettoyees;

View File

@ -3,6 +3,7 @@ export type MessageDeNettoyage = {
severite: severite:
| "Donnée ignorée" | "Donnée ignorée"
| "Donnée suspecte" | "Donnée suspecte"
| "Donnée corrigée"
| "Donnée incohérente bloquante"; | "Donnée incohérente bloquante";
}; };
export function msgDonneeIgnoree(message: string): MessageDeNettoyage { export function msgDonneeIgnoree(message: string): MessageDeNettoyage {
@ -11,6 +12,12 @@ export function msgDonneeIgnoree(message: string): MessageDeNettoyage {
severite: "Donnée ignorée", severite: "Donnée ignorée",
}; };
} }
export function msgDonneeCorrigee(message: string): MessageDeNettoyage {
return {
message,
severite: "Donnée corrigée",
};
}
export function msgDonneeSuspecte(message: string): MessageDeNettoyage { export function msgDonneeSuspecte(message: string): MessageDeNettoyage {
return { return {
message, message,

View File

@ -0,0 +1,19 @@
import { Famille } from "../../Famille";
import { FonctionNettoyage } from "./FonctionNettoyage";
import { MessageDeNettoyage } from "./MessageDeNettoyage";
import { DonneesNettoyees } from "./DonneesNettoyees";
export function appliquerLesFonctionsDeNettoyages(
familles: Famille[],
fonctionsDeNettoyage: FonctionNettoyage[]
): DonneesNettoyees {
let famillesNettoyees = familles;
let messages: MessageDeNettoyage[] = [];
for (const fn of fonctionsDeNettoyage) {
const donneesNettoyees: DonneesNettoyees = fn.apply(null, [familles]);
messages = [...messages, ...donneesNettoyees.messages];
famillesNettoyees = donneesNettoyees.familles;
}
return { familles: famillesNettoyees, messages };
}

View File

@ -1,57 +0,0 @@
import { Famille } from "../Famille";
import { checkDataConsistency } from "./checkDataConsistency";
import {
MessageDeNettoyage,
msgDonneeBloquante,
msgDonneeSuspecte,
} from "./MessageDeNettoyage";
import { supprimerLesEntreesVide } from "./supprimerLesEntreesVide";
import { supprimerLesEvenementsHorsResisstance } from "./supprimerLesEvenementsHorsResisstance";
import { supprimerLesEvenementsSansDate } from "./supprimerLesEvenementsSansDate";
export type DonneesNettoyees = {
familles: Famille[];
messages: MessageDeNettoyage[];
};
export function nettoyerDonneesFamilles(
donneesFamillesBrutes: Famille[]
): DonneesNettoyees {
let familles = donneesFamillesBrutes;
let messages: MessageDeNettoyage[] = [];
const output1 = supprimerLesEntreesVide(familles);
messages = [...messages, ...output1.messages];
familles = output1.familles;
// TODO convert checkDataConsistency to filters
const consistencyReport = checkDataConsistency(familles);
// Adapte les message
const errorMessages: MessageDeNettoyage[] = consistencyReport.errors.map(
(e) => msgDonneeBloquante(`${e.familyId} - ${e.issueType}`)
);
const warnings: MessageDeNettoyage[] = consistencyReport.warnings.map((e) =>
msgDonneeSuspecte(`${e.familyId} - ${e.issueType}`)
);
messages = [...messages, ...errorMessages, ...warnings];
type FonctionNettoyage = (familles: Famille[]) => DonneesNettoyees;
const fonctionsDeNettoyage: FonctionNettoyage[] = [
supprimerLesEvenementsSansDate,
supprimerLesEvenementsHorsResisstance,
];
for (const fn of fonctionsDeNettoyage) {
const donneesNettoyees: DonneesNettoyees = fn.apply(null, [familles]);
messages = [...messages, ...donneesNettoyees.messages];
familles = donneesNettoyees.familles;
}
return {
familles,
messages,
};
}

View File

@ -16,8 +16,8 @@ import { formatDate } from "date-fns";
import { computeStatsGeneralesMensuelles } from "./statistiques/v2/generales/computeStatsGeneralesMensuelles"; import { computeStatsGeneralesMensuelles } from "./statistiques/v2/generales/computeStatsGeneralesMensuelles";
import { mermaidDiagramStatsGeneralesMensuelles } from "./statistiques/v2/generales/mermaidDiagramStatsGeneralesMensuelles"; import { mermaidDiagramStatsGeneralesMensuelles } from "./statistiques/v2/generales/mermaidDiagramStatsGeneralesMensuelles";
import { publishStatsGenerales } from "./notion/publish/v2/publishStatsGenerales"; import { publishStatsGenerales } from "./notion/publish/v2/publishStatsGenerales";
import { nettoyerDonneesFamilles } from "./data/nettoyage/nettoyerDonneesFamilles";
import { typeEvenementsProcedurePenale } from "./data/TypeEvenementsPenal"; import { typeEvenementsProcedurePenale } from "./data/TypeEvenementsPenal";
import { nettoyerDonneesFamilles } from "./data/nettoyage/familles/nettoyerDonneesFamilles";
type ProcessOptions = { type ProcessOptions = {
dryRun: boolean; dryRun: boolean;
@ -65,6 +65,9 @@ function buildProcessOptions(): ProcessOptions {
const donneesSuspectes = messages.filter( const donneesSuspectes = messages.filter(
(m) => m.severite === "Donnée suspecte" (m) => m.severite === "Donnée suspecte"
); );
const donneesCorrigees = messages.filter(
(m) => m.severite === "Donnée corrigée"
);
const donneesIgnorees = messages.filter( const donneesIgnorees = messages.filter(
(m) => m.severite === "Donnée ignorée" (m) => m.severite === "Donnée ignorée"
); );
@ -84,6 +87,12 @@ function buildProcessOptions(): ProcessOptions {
console.warn(" - " + e.message); console.warn(" - " + e.message);
}); });
} }
if (donneesCorrigees.length > 0) {
console.warn(`${donneesCorrigees.length} Données corrigées:`);
donneesCorrigees.forEach((e) => {
console.warn(" - " + e.message);
});
}
if (donneesIgnorees.length > 0) { if (donneesIgnorees.length > 0) {
console.warn(`${donneesIgnorees.length} Données ignorées:`); console.warn(`${donneesIgnorees.length} Données ignorées:`);
donneesIgnorees.forEach((e) => { donneesIgnorees.forEach((e) => {

View File

@ -5,6 +5,7 @@ import {
} from "../../data/EvenementFamille"; } from "../../data/EvenementFamille";
import { Famille } from "../../data/Famille"; import { Famille } from "../../data/Famille";
import { percent } from "../../utils/math/percent"; import { percent } from "../../utils/math/percent";
import { statutExResistant, statutResistant } from "../../data/StatutFamille";
type FamillesWithEventsConditionInEarlyPeriod = { type FamillesWithEventsConditionInEarlyPeriod = {
[name: string]: { [name: string]: {
@ -32,7 +33,7 @@ export const computeFamillesWithEventsConditionInEarlyPeriod = (
Object.entries(durations).map(([name, months]) => { Object.entries(durations).map(([name, months]) => {
const famillesWithAtLeastDurationOfDc = familles const famillesWithAtLeastDurationOfDc = familles
.filter( .filter(
(f) => f.Statut === "Résistant·e" || f.Statut === "Ex résistant·e" (f) => f.Statut === statutResistant || f.Statut === statutExResistant
) )
.filter( .filter(
(f) => (f) =>