mirror of
https://framagit.org/enfance-libre/statistiques
synced 2025-12-07 07:23:44 +00:00
feat: Revue des stats sur + draft pénale et civile
This commit is contained in:
parent
af0fba621a
commit
f328137c30
24 changed files with 515 additions and 264 deletions
6
src/data/ContexteEntreeDC.ts
Normal file
6
src/data/ContexteEntreeDC.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export type ContexteEntreeDC =
|
||||
| "Pas de demande (Plein droit)"
|
||||
| "Pas de demande"
|
||||
| "Après refus"
|
||||
| "Après mise en demeure"
|
||||
| "Après poursuite procureur";
|
||||
70
src/data/EvenementFamille.ts
Normal file
70
src/data/EvenementFamille.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
import { Period } from "../period/Period";
|
||||
import { isPeriodContaining } from "../period/isPeriodContaining";
|
||||
import { TypeEvenement } from "./TypeEvenement";
|
||||
|
||||
export type EvenementFamille = {
|
||||
notionId: string;
|
||||
notionIdFamille: string;
|
||||
Évènement: string;
|
||||
Date: Date | null;
|
||||
Type: TypeEvenement;
|
||||
"Enfants concernés": string;
|
||||
};
|
||||
|
||||
export function isProcedurePenale(evenement: EvenementFamille): boolean {
|
||||
return categorieEvenement[evenement.Type] === "Procédure Pénale";
|
||||
}
|
||||
|
||||
export function isProcedureCivile(evenement: EvenementFamille): boolean {
|
||||
return categorieEvenement[evenement.Type] === "Procédure Civile";
|
||||
}
|
||||
|
||||
const categorieEvenement: {
|
||||
[evt in TypeEvenement]: CategorieEvenement;
|
||||
} = {
|
||||
["Récidive gendarmerie"]: "Procédure Pénale",
|
||||
["Appel du jugement"]: "Procédure Pénale",
|
||||
["Tribunal de police judiciaire"]: "Procédure Pénale",
|
||||
["Signalement au procureur"]: "Procédure Civile", // TBC
|
||||
["Mise en demeure de scolarisation"]: "Procédure Pénale",
|
||||
["Signalement"]: "Procédure Civile",
|
||||
["Audition gendarmerie / police"]: "Procédure Pénale",
|
||||
["Convocation procureur"]: "Procédure Pénale",
|
||||
["Audition procureur"]: "Procédure Pénale",
|
||||
["Composition pénale refusée"]: "Procédure Pénale",
|
||||
["Composition pénale acceptée"]: "Procédure Pénale",
|
||||
["Classement social sans suite"]: "Procédure Civile",
|
||||
["Classement pénal sans suite"]: "Procédure Pénale",
|
||||
["Enquête sociale"]: "Procédure Civile",
|
||||
["Information préoccupante"]: "Procédure Civile",
|
||||
["Juge pour enfants"]: "Procédure Civile",
|
||||
["Tribunal correctionnel"]: "Procédure Pénale",
|
||||
["Convocation CRPC"]: "Procédure Pénale",
|
||||
["Plaidoirie"]: "Procédure Pénale",
|
||||
["Audience CRPC"]: "Procédure Pénale",
|
||||
["Refus CRPC"]: "Procédure Pénale",
|
||||
["Audition des enfants"]: "Procédure Civile",
|
||||
["Assistance éducative"]: "Procédure Civile",
|
||||
["Contrôle forcé"]: "Autre",
|
||||
["Refus de contrôle"]: "Autre",
|
||||
["Rappel à la loi"]: "Procédure Pénale",
|
||||
["Passage police municipale"]: "Procédure Pénale",
|
||||
["Administrateur AD'HOC"]: "Autre",
|
||||
["Validation désobéissance"]: "Autre",
|
||||
};
|
||||
|
||||
export type CategorieEvenement =
|
||||
| "Procédure Pénale"
|
||||
| "Procédure Civile"
|
||||
| "Autre";
|
||||
|
||||
export function isEvenementInPeriod(
|
||||
evt: EvenementFamille,
|
||||
period: Period
|
||||
): unknown {
|
||||
return evt.Date && isPeriodContaining(period, evt.Date);
|
||||
}
|
||||
|
||||
export function isEvenementBefore(evt: EvenementFamille, date: Date): unknown {
|
||||
return evt.Date !== null && evt.Date < date;
|
||||
}
|
||||
|
|
@ -2,6 +2,9 @@ import { differenceInDays } from "date-fns";
|
|||
import { Period } from "../period/Period";
|
||||
import { arePeriodsOverlaping } from "../period/arePeriodsOverlaping";
|
||||
import { isPeriodContaining } from "../period/isPeriodContaining";
|
||||
import { ContexteEntreeDC } from "./ContexteEntreeDC";
|
||||
import { EvenementFamille } from "./EvenementFamille";
|
||||
import { StatutFamille } from "./StatutFamille";
|
||||
|
||||
export type Famille = {
|
||||
notionId: string;
|
||||
|
|
@ -13,38 +16,6 @@ export type Famille = {
|
|||
Evenements: EvenementFamille[];
|
||||
};
|
||||
|
||||
export type EvenementFamille = {
|
||||
notionId: string;
|
||||
notionIdFamille: string;
|
||||
Évènement: string;
|
||||
Date: Date | null;
|
||||
Type: TypeEvenement;
|
||||
"Enfants concernés": string;
|
||||
};
|
||||
|
||||
export type ContexteEntreeDC =
|
||||
| "Pas de demande (Plein droit)"
|
||||
| "Pas de demande"
|
||||
| "Après refus"
|
||||
| "Après mise en demeure"
|
||||
| "Après poursuite procureur";
|
||||
|
||||
export type TypeEvenement =
|
||||
| "Mise en demeure de scolarisation"
|
||||
| "Composition pénale refusée"
|
||||
| "Composition pénale acceptée";
|
||||
|
||||
export type StatutFamille =
|
||||
| "Résistant.e"
|
||||
| "Ex résistant·e·s"
|
||||
| "À préciser"
|
||||
| "Se questionne"
|
||||
| "Désobéissence décidée"
|
||||
| "Rédaction Déclaration"
|
||||
| "Déclaration Validée - Attente éléments"
|
||||
| "Abdandon"
|
||||
| "Incompatible";
|
||||
|
||||
export function periodOfResistance(
|
||||
family: Famille,
|
||||
atDate: Date = new Date(Date.now())
|
||||
|
|
|
|||
10
src/data/StatutFamille.ts
Normal file
10
src/data/StatutFamille.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export type StatutFamille =
|
||||
| "Résistant.e"
|
||||
| "Ex résistant·e·s"
|
||||
| "À préciser"
|
||||
| "Se questionne"
|
||||
| "Désobéissence décidée"
|
||||
| "Rédaction Déclaration"
|
||||
| "Déclaration Validée - Attente éléments"
|
||||
| "Abdandon"
|
||||
| "Incompatible";
|
||||
30
src/data/TypeEvenement.ts
Normal file
30
src/data/TypeEvenement.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
export type TypeEvenement =
|
||||
| "Récidive gendarmerie"
|
||||
| "Appel du jugement"
|
||||
| "Tribunal de police judiciaire"
|
||||
| "Signalement au procureur"
|
||||
| "Mise en demeure de scolarisation"
|
||||
| "Signalement"
|
||||
| "Audition gendarmerie / police"
|
||||
| "Convocation procureur"
|
||||
| "Audition procureur"
|
||||
| "Composition pénale refusée"
|
||||
| "Composition pénale acceptée"
|
||||
| "Classement social sans suite"
|
||||
| "Classement pénal sans suite"
|
||||
| "Enquête sociale"
|
||||
| "Information préoccupante"
|
||||
| "Juge pour enfants"
|
||||
| "Tribunal correctionnel"
|
||||
| "Convocation CRPC"
|
||||
| "Plaidoirie"
|
||||
| "Audience CRPC"
|
||||
| "Refus CRPC"
|
||||
| "Audition des enfants"
|
||||
| "Assistance éducative"
|
||||
| "Contrôle forcé"
|
||||
| "Refus de contrôle"
|
||||
| "Rappel à la loi"
|
||||
| "Passage police municipale"
|
||||
| "Administrateur AD'HOC"
|
||||
| "Validation désobéissance";
|
||||
|
|
@ -30,7 +30,7 @@ import { computeELStats } from "./statistiques/computeELStats";
|
|||
const currentDate = new Date(Date.now());
|
||||
|
||||
console.log("Building statistics...");
|
||||
const resistantCountStats = computeELStats(families);
|
||||
const resistantCountStats = computeELStats(families, currentDate);
|
||||
|
||||
console.log("Publishing statistics...");
|
||||
publishStatisticsToNotion(resistantCountStats, currentDate, notionClient);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import { Client, isFullPage } from "@notionhq/client";
|
||||
import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
|
||||
import {
|
||||
ContexteEntreeDC,
|
||||
EvenementFamille,
|
||||
Famille,
|
||||
StatutFamille,
|
||||
TypeEvenement,
|
||||
} from "../../data/Famille";
|
||||
import { ContexteEntreeDC } from "../../data/ContexteEntreeDC";
|
||||
import { EvenementFamille } from "../../data/EvenementFamille";
|
||||
import { Famille } from "../../data/Famille";
|
||||
import { StatutFamille } from "../../data/StatutFamille";
|
||||
import { TypeEvenement } from "../../data/TypeEvenement";
|
||||
import { datePropertyToDate } from "../utils/properties/datePropertyToDate";
|
||||
import { relationPropertyToPageId } from "../utils/properties/relationPropertyToPageId";
|
||||
import { selectPropertyToText } from "../utils/properties/selectPropertyToText";
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { StatPublishOptions } from "../../statPropsPublishOptions";
|
||||
import { StatPublishOptions } from "../../statPublishOptions";
|
||||
|
||||
export function formatValue(value: number, publishOptions: StatPublishOptions) {
|
||||
const valueStr = value.toLocaleString("fr-FR", {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { ValueWithEvol } from "../../../statistiques/ELStats";
|
||||
import { StatPublishOptions } from "../../statPropsPublishOptions";
|
||||
import { StatPublishOptions } from "../../statPublishOptions";
|
||||
import { formatValue } from "./formatValue";
|
||||
|
||||
export function formatValueWithEvol(
|
||||
|
|
|
|||
|
|
@ -1,21 +1,30 @@
|
|||
import { Client, isFullPage } from "@notionhq/client";
|
||||
import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
|
||||
import { ELPeriodStats, ValueWithEvol } from "../../statistiques/ELStats";
|
||||
import {
|
||||
StatPublishOptions,
|
||||
statPropsPublishOptions,
|
||||
} from "../statPropsPublishOptions";
|
||||
PageObjectResponse,
|
||||
UpdateDatabaseParameters,
|
||||
} from "@notionhq/client/build/src/api-endpoints";
|
||||
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";
|
||||
|
||||
const periodeDbPropertyName = "Période";
|
||||
|
||||
export async function publishPeriodStats(
|
||||
notionClient: Client,
|
||||
periodStatsDbId: string,
|
||||
stats: ELPeriodStats[]
|
||||
statsPeriods: ELStatsPeriod[]
|
||||
) {
|
||||
if (statsPeriods.length > 0) {
|
||||
await updateDbProps(notionClient, periodStatsDbId, statsPeriods[0].stats);
|
||||
}
|
||||
const periodRows = (
|
||||
await queryAllDbResults(notionClient, {
|
||||
database_id: periodStatsDbId,
|
||||
|
|
@ -24,11 +33,14 @@ export async function publishPeriodStats(
|
|||
|
||||
const indexedPeriodRows: { [period: string]: PageObjectResponse } =
|
||||
Object.fromEntries(
|
||||
periodRows.map((r) => [titlePropertyToText(r.properties, "Période"), r])
|
||||
periodRows.map((r) => [
|
||||
titlePropertyToText(r.properties, periodeDbPropertyName),
|
||||
r,
|
||||
])
|
||||
);
|
||||
|
||||
const indexedPeriodStats = Object.fromEntries(
|
||||
stats.map((stat) => [stat.periodId, stat])
|
||||
statsPeriods.map((stat) => [stat.periodId, stat])
|
||||
);
|
||||
|
||||
const rowIdsToDelete = Object.entries(indexedPeriodRows)
|
||||
|
|
@ -76,15 +88,59 @@ export async function publishPeriodStats(
|
|||
}
|
||||
}
|
||||
|
||||
async function updateDbProps(
|
||||
notionClient: Client,
|
||||
periodStatsDbId: string,
|
||||
stats: PeriodStatsValues<ValueWithEvol>
|
||||
) {
|
||||
const db = await notionClient.databases.retrieve({
|
||||
database_id: periodStatsDbId,
|
||||
});
|
||||
const statsNotionProps: UpdateDatabaseParameters["properties"] =
|
||||
Object.fromEntries(
|
||||
(Object.keys(stats) as Array<keyof PeriodStatsValues<ValueWithEvol>>).map(
|
||||
(jsProp) => {
|
||||
const publishOptions = statPublishOptions(jsProp);
|
||||
return [
|
||||
publishOptions.notionPropName,
|
||||
{
|
||||
rich_text: {},
|
||||
},
|
||||
];
|
||||
}
|
||||
)
|
||||
);
|
||||
const propsToRemove = Object.fromEntries(
|
||||
Object.keys(db.properties)
|
||||
.filter(
|
||||
(k) =>
|
||||
!Object.prototype.hasOwnProperty.call(statsNotionProps, k) &&
|
||||
k !== periodeDbPropertyName
|
||||
)
|
||||
.map((k) => [k, null])
|
||||
);
|
||||
|
||||
await notionClient.databases.update({
|
||||
database_id: periodStatsDbId,
|
||||
properties: {
|
||||
[periodeDbPropertyName]: {
|
||||
title: {},
|
||||
},
|
||||
...statsNotionProps,
|
||||
...propsToRemove,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildRowPropertiesForUpsert(
|
||||
periodId: string,
|
||||
stats: ELPeriodStats["stats"]
|
||||
stats: PeriodStatsValues<ValueWithEvol>
|
||||
): CreatePageProperties {
|
||||
const statsNotionProps: CreatePageProperties = Object.fromEntries(
|
||||
(Object.keys(stats) as Array<keyof ELPeriodStats["stats"]>).map(
|
||||
(Object.keys(stats) as Array<keyof PeriodStatsValues<ValueWithEvol>>).map(
|
||||
(jsProp) => {
|
||||
const value: ValueWithEvol = stats[jsProp];
|
||||
const publishOptions = statPropsPublishOptions[jsProp];
|
||||
const publishOptions = statPublishOptions(jsProp);
|
||||
return [
|
||||
publishOptions.notionPropName,
|
||||
valueWithEvolProp(value, publishOptions),
|
||||
|
|
@ -93,7 +149,7 @@ function buildRowPropertiesForUpsert(
|
|||
)
|
||||
);
|
||||
return {
|
||||
Période: {
|
||||
[periodeDbPropertyName]: {
|
||||
title: [
|
||||
{
|
||||
text: {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
import { Client, isFullBlock } from "@notionhq/client";
|
||||
import { BlockObjectRequest } from "@notionhq/client/build/src/api-endpoints";
|
||||
import { ELStatsActuelles } from "../../statistiques/ELStats";
|
||||
import {
|
||||
StatPublishOptions,
|
||||
statPropsPublishOptions,
|
||||
} from "../statPropsPublishOptions";
|
||||
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";
|
||||
|
|
@ -13,13 +10,13 @@ import { currentStatsHeading, statsPageId } from "./publishStatisticsToNotion";
|
|||
|
||||
export async function publishStatsActuelles(
|
||||
notionClient: Client,
|
||||
statsActuelles: ELStatsActuelles
|
||||
statsActuelles: ELStatsAtDate<number>
|
||||
) {
|
||||
const newBlocks = (
|
||||
Object.keys(statsActuelles) as Array<keyof ELStatsActuelles>
|
||||
Object.keys(statsActuelles) as Array<keyof typeof statsActuelles>
|
||||
).map((jsProp) => {
|
||||
const value: number = statsActuelles[jsProp];
|
||||
const publishOptions = statPropsPublishOptions[jsProp];
|
||||
const publishOptions = statPublishOptions(jsProp);
|
||||
return currentStatBlock(value, publishOptions);
|
||||
});
|
||||
await updateStatsActuellesBlocks(notionClient, newBlocks);
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
import { ELPeriodStats, ELStatsActuelles } from "../statistiques/ELStats";
|
||||
|
||||
export const statPropsPublishOptions: {
|
||||
[jsPropName in statsPropNames]: StatPublishOptions;
|
||||
} = {
|
||||
nbFamilleResistantes: {
|
||||
notionPropName: "Nb familles résistantes",
|
||||
},
|
||||
nbFamilleResistantesOrEx: {
|
||||
notionPropName: "Nb familles résistantes ou ex-résistantes",
|
||||
},
|
||||
dureeResistanceMoyenne: {
|
||||
notionPropName: "Durée résistance moyenne",
|
||||
unit: "j",
|
||||
},
|
||||
dureeResistanceMediane: {
|
||||
notionPropName: "Durée résistance médiane",
|
||||
unit: "j",
|
||||
},
|
||||
nbFamillesMisesEnDemeure: {
|
||||
notionPropName: "Nb familles mises en demeure",
|
||||
},
|
||||
pourcentageFamillesMisesEnDemeure: {
|
||||
notionPropName: "Pourcentage de familles mises en demeure",
|
||||
unit: "%",
|
||||
},
|
||||
pourcentageEntreeApresMiseEnDemeure: {
|
||||
notionPropName: "Pourcentage d'entrées après mises en demeure",
|
||||
unit: "%",
|
||||
},
|
||||
};
|
||||
export type statsPropNames =
|
||||
| keyof ELPeriodStats["stats"]
|
||||
| keyof ELStatsActuelles;
|
||||
export type StatPublishOptions = {
|
||||
notionPropName: string;
|
||||
unit?: string;
|
||||
valueMaxFractioDigits?: number;
|
||||
evolMaxFractioDigits?: number;
|
||||
evolPctMaxFractioDigits?: number;
|
||||
};
|
||||
71
src/notion/statPublishOptions.ts
Normal file
71
src/notion/statPublishOptions.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import { AllStatsPropNames } from "../statistiques/ELStats";
|
||||
|
||||
export function statPublishOptions(
|
||||
statJsPropName: AllStatsPropNames
|
||||
): StatPublishOptions {
|
||||
return statPropsPublishOptions[statJsPropName];
|
||||
}
|
||||
const statPropsPublishOptions: {
|
||||
[jsPropName in AllStatsPropNames]: StatPublishOptions;
|
||||
} = {
|
||||
nbFamilleResistantes: {
|
||||
notionPropName: "Nb familles résistantes",
|
||||
},
|
||||
|
||||
nbFamilleResistantesSurPeriode: {
|
||||
notionPropName: "Nb familles résistantes sur période",
|
||||
},
|
||||
nbFamilleResistantesOrEx: {
|
||||
notionPropName: "Nb familles résistantes ou ex-résistantes",
|
||||
},
|
||||
dureeResistanceMoyenne: {
|
||||
notionPropName: "Durée résistance moyenne",
|
||||
unit: "j",
|
||||
},
|
||||
dureeResistanceMediane: {
|
||||
notionPropName: "Durée résistance médiane",
|
||||
unit: "j",
|
||||
},
|
||||
nbFamillesMisesEnDemeure: {
|
||||
notionPropName: "Nb familles mises en demeure",
|
||||
},
|
||||
pourcentageFamillesMisesEnDemeure: {
|
||||
notionPropName: "% de familles mises en demeure",
|
||||
unit: "%",
|
||||
},
|
||||
pourcentageEntreeApresMiseEnDemeure: {
|
||||
notionPropName: "% d'entrées après mises en demeure",
|
||||
unit: "%",
|
||||
},
|
||||
pourcentageEntreeApresMiseEnDemeureSurPeriode: {
|
||||
notionPropName: "% d'entrées après mises en demeure sur période",
|
||||
},
|
||||
nbFamillesProcedurePenale: {
|
||||
notionPropName: "Nb familles avec procédure pénale",
|
||||
},
|
||||
pourcentageFamillesProcedurePenale: {
|
||||
notionPropName: "% familles avec procédure pénale",
|
||||
unit: "%",
|
||||
},
|
||||
nbFamilleAvecProcedurePenaleSurPeriode: {
|
||||
notionPropName: "Nb familles avec procédure pénale sur période",
|
||||
},
|
||||
|
||||
nbFamillesProcedureCivile: {
|
||||
notionPropName: "Nb familles avec procédure civile",
|
||||
},
|
||||
pourcentageFamillesProcedureCivile: {
|
||||
notionPropName: "% familles avec procédure civile",
|
||||
unit: "%",
|
||||
},
|
||||
nbFamilleAvecProcedureCivileSurPeriode: {
|
||||
notionPropName: "Nb familles avec procédure civile sur période",
|
||||
},
|
||||
};
|
||||
export type StatPublishOptions = {
|
||||
notionPropName: string;
|
||||
unit?: string;
|
||||
valueMaxFractioDigits?: number;
|
||||
evolMaxFractioDigits?: number;
|
||||
evolPctMaxFractioDigits?: number;
|
||||
};
|
||||
|
|
@ -1,29 +1,36 @@
|
|||
export type ELStats = {
|
||||
actuelles: ELStatsActuelles;
|
||||
annees: ELPeriodStats[];
|
||||
actuelles: ELStatsAtDate<number>;
|
||||
annees: ELStatsPeriod[];
|
||||
|
||||
mois: ELPeriodStats[];
|
||||
};
|
||||
export type ELStatsActuelles = {
|
||||
nbFamilleResistantes: number;
|
||||
nbFamilleResistantesOrEx: number;
|
||||
dureeResistanceMoyenne: number;
|
||||
dureeResistanceMediane: number;
|
||||
nbFamillesMisesEnDemeure: number;
|
||||
pourcentageFamillesMisesEnDemeure: number;
|
||||
pourcentageEntreeApresMiseEnDemeure: number;
|
||||
mois: ELStatsPeriod[];
|
||||
};
|
||||
|
||||
export type ELPeriodStats = {
|
||||
export type ELStatsAtDate<V> = {
|
||||
nbFamilleResistantes: V;
|
||||
nbFamilleResistantesOrEx: V;
|
||||
dureeResistanceMoyenne: V;
|
||||
dureeResistanceMediane: V;
|
||||
nbFamillesMisesEnDemeure: V;
|
||||
pourcentageFamillesMisesEnDemeure: V;
|
||||
pourcentageEntreeApresMiseEnDemeure: V;
|
||||
nbFamillesProcedurePenale: V;
|
||||
pourcentageFamillesProcedurePenale: V;
|
||||
nbFamillesProcedureCivile: V;
|
||||
pourcentageFamillesProcedureCivile: V;
|
||||
};
|
||||
|
||||
export type ELStatsPeriod = {
|
||||
periodId: string;
|
||||
stats: {
|
||||
nbFamilleResistantes: ValueWithEvol;
|
||||
nbFamilleResistantesOrEx: ValueWithEvol;
|
||||
dureeResistanceMoyenne: ValueWithEvol;
|
||||
dureeResistanceMediane: ValueWithEvol;
|
||||
nbFamillesMisesEnDemeure: ValueWithEvol;
|
||||
pourcentageEntreeApresMiseEnDemeure: ValueWithEvol;
|
||||
};
|
||||
stats: PeriodStatsValues<ValueWithEvol>;
|
||||
};
|
||||
|
||||
export type PeriodStatsValues<V> = ELStatsAtDate<V> | ELStatsOverPeriod<V>;
|
||||
|
||||
export type ELStatsOverPeriod<V> = {
|
||||
pourcentageEntreeApresMiseEnDemeureSurPeriode: V;
|
||||
nbFamilleResistantesSurPeriode: V;
|
||||
nbFamilleAvecProcedurePenaleSurPeriode: V;
|
||||
nbFamilleAvecProcedureCivileSurPeriode: V;
|
||||
};
|
||||
|
||||
export type ValueWithEvol = {
|
||||
|
|
@ -31,3 +38,7 @@ export type ValueWithEvol = {
|
|||
evol: number;
|
||||
evolPercent: number;
|
||||
};
|
||||
|
||||
export type AllStatsPropNames =
|
||||
| keyof ELStatsOverPeriod<number>
|
||||
| keyof ELStatsAtDate<number>;
|
||||
|
|
|
|||
|
|
@ -1,94 +1,68 @@
|
|||
import {
|
||||
Famille,
|
||||
dureeResistanceInDays,
|
||||
isResistantOverPeriod,
|
||||
periodOfResistance,
|
||||
} from "../data/Famille";
|
||||
import { Famille } from "../data/Famille";
|
||||
import { IdentifiedPeriod } from "../period/IdentifiedPeriod";
|
||||
import { isPeriodContaining } from "../period/isPeriodContaining";
|
||||
import { ELPeriodStats, ValueWithEvol } from "./ELStats";
|
||||
import { average } from "./math/average";
|
||||
import { median } from "./math/median";
|
||||
import { Period } from "../period/Period";
|
||||
import {
|
||||
ELStatsOverPeriod,
|
||||
ELStatsPeriod,
|
||||
PeriodStatsValues,
|
||||
ValueWithEvol,
|
||||
} from "./ELStats";
|
||||
import { computeELStatsAtDate } from "./computeELStatsAtDate";
|
||||
import { computeELStatsOverPeriod } from "./computeELStatsOverPeriod";
|
||||
|
||||
export function computeELPeriodStats(
|
||||
familles: Famille[],
|
||||
periods: IdentifiedPeriod[]
|
||||
): ELPeriodStats[] {
|
||||
const periodStats: ELPeriodStats[] = [];
|
||||
let previousELPeriodStats: ELPeriodStats["stats"] | null = null;
|
||||
for (const period of periods) {
|
||||
const periodEndOrNow =
|
||||
period.end.getTime() > Date.now() ? new Date(Date.now()) : period.end;
|
||||
): ELStatsPeriod[] {
|
||||
let previousPeriodStatNumberValues: PeriodStatsValues<number> | null = null;
|
||||
return periods.map((period) => {
|
||||
const periodStatNumberValues: PeriodStatsValues<number> =
|
||||
computePeriodStatsNumberValues(familles, period);
|
||||
|
||||
const nbFamilleResistantes = familles.filter((famille) =>
|
||||
isResistantOverPeriod(famille, period)
|
||||
).length;
|
||||
|
||||
const nbFamilleResistantesOrEx = familles.filter((famille) => {
|
||||
const por = periodOfResistance(famille, periodEndOrNow);
|
||||
return por !== null && por.start < periodEndOrNow;
|
||||
}).length;
|
||||
|
||||
const dureesResistances = familles
|
||||
.map((famille) => dureeResistanceInDays(famille, periodEndOrNow))
|
||||
.filter(notNull);
|
||||
const dureeResistanceMediane = Math.round(median(dureesResistances));
|
||||
const dureeResistanceMoyenne = Math.round(average(dureesResistances));
|
||||
|
||||
const nbFamillesMiseEnDemeure = familles.filter((famille) =>
|
||||
famille.Evenements.find(
|
||||
(e) =>
|
||||
e.Type === "Mise en demeure de scolarisation" &&
|
||||
e.Date &&
|
||||
isPeriodContaining(period, e.Date)
|
||||
)
|
||||
).length;
|
||||
const famillesEntreesOverPeriod = familles.filter((f) => {
|
||||
const por = periodOfResistance(f);
|
||||
return por != null && isPeriodContaining(period, por.start);
|
||||
});
|
||||
const entreeApresMiseEnDemeure = famillesEntreesOverPeriod.filter(
|
||||
(f) =>
|
||||
f.ContexteEntree === "Après mise en demeure" ||
|
||||
f.ContexteEntree === "Après poursuite procureur"
|
||||
// Compute evol
|
||||
const statsWithEvol = computeStatsEvol(
|
||||
periodStatNumberValues,
|
||||
previousPeriodStatNumberValues
|
||||
);
|
||||
const pourcentageEntreeApresMiseEnDemeure =
|
||||
(100 * entreeApresMiseEnDemeure.length) /
|
||||
famillesEntreesOverPeriod.length;
|
||||
|
||||
const stats: ELPeriodStats = {
|
||||
previousPeriodStatNumberValues = periodStatNumberValues;
|
||||
const periodStats: ELStatsPeriod = {
|
||||
periodId: period.id,
|
||||
stats: {
|
||||
nbFamilleResistantes: valueWithEvol(
|
||||
nbFamilleResistantes,
|
||||
previousELPeriodStats?.nbFamilleResistantes.value
|
||||
),
|
||||
nbFamilleResistantesOrEx: valueWithEvol(
|
||||
nbFamilleResistantesOrEx,
|
||||
previousELPeriodStats?.nbFamilleResistantesOrEx.value
|
||||
),
|
||||
dureeResistanceMediane: valueWithEvol(
|
||||
dureeResistanceMediane,
|
||||
previousELPeriodStats?.dureeResistanceMediane.value
|
||||
),
|
||||
dureeResistanceMoyenne: valueWithEvol(
|
||||
dureeResistanceMoyenne,
|
||||
previousELPeriodStats?.dureeResistanceMoyenne.value
|
||||
),
|
||||
nbFamillesMisesEnDemeure: valueWithEvol(
|
||||
nbFamillesMiseEnDemeure,
|
||||
previousELPeriodStats?.nbFamillesMisesEnDemeure.value
|
||||
),
|
||||
pourcentageEntreeApresMiseEnDemeure: valueWithEvol(
|
||||
pourcentageEntreeApresMiseEnDemeure,
|
||||
previousELPeriodStats?.pourcentageEntreeApresMiseEnDemeure.value
|
||||
),
|
||||
},
|
||||
stats: statsWithEvol,
|
||||
};
|
||||
periodStats.push(stats);
|
||||
previousELPeriodStats = stats?.stats;
|
||||
}
|
||||
return periodStats;
|
||||
return periodStats;
|
||||
});
|
||||
}
|
||||
|
||||
function computePeriodStatsNumberValues(
|
||||
familles: Famille[],
|
||||
period: Period
|
||||
): PeriodStatsValues<number> {
|
||||
const statsAtPeriodEnd = computeELStatsAtDate(familles, period.end);
|
||||
|
||||
const statsOverPeriod: ELStatsOverPeriod<number> = computeELStatsOverPeriod(
|
||||
familles,
|
||||
period
|
||||
);
|
||||
return {
|
||||
...statsAtPeriodEnd,
|
||||
...statsOverPeriod,
|
||||
};
|
||||
}
|
||||
|
||||
function computeStatsEvol(
|
||||
periodStatNumberValues: PeriodStatsValues<number>,
|
||||
previousPeriodStatNumberValues: PeriodStatsValues<number> | null | undefined
|
||||
): PeriodStatsValues<ValueWithEvol> {
|
||||
return Object.fromEntries(
|
||||
(
|
||||
Object.entries(periodStatNumberValues) as Array<
|
||||
[prop: keyof PeriodStatsValues<number>, number]
|
||||
>
|
||||
).map(([k, v]) => [
|
||||
k,
|
||||
valueWithEvol(v, previousPeriodStatNumberValues?.[k]),
|
||||
])
|
||||
) as PeriodStatsValues<ValueWithEvol>;
|
||||
}
|
||||
|
||||
function valueWithEvol(
|
||||
|
|
@ -111,9 +85,3 @@ function evol(current: number, previous: number | undefined): number {
|
|||
if (previous === undefined) return NaN;
|
||||
return current - previous;
|
||||
}
|
||||
|
||||
// Typescript is not able to infer this inline see
|
||||
// https://stackoverflow.com/questions/43118692/typescript-filter-out-nulls-from-an-array
|
||||
function notNull<TValue>(value: TValue | null): value is TValue {
|
||||
return value !== null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
import { Famille } from "../data/Famille";
|
||||
import { ELStats } from "./ELStats";
|
||||
import { computeELPeriodStats } from "./computeELPeriodStats";
|
||||
import { computeStatsActuelles } from "./computeStatsActuelles";
|
||||
import { computeELStatsAtDate } from "./computeELStatsAtDate";
|
||||
import { generateELMonths } from "./generateELMonths";
|
||||
import { generateELYears } from "./generateELYears";
|
||||
|
||||
export function computeELStats(families: Famille[]): ELStats {
|
||||
const actuelles = computeStatsActuelles(families);
|
||||
const elYears = generateELYears();
|
||||
const yearsStats = computeELPeriodStats(families, elYears);
|
||||
export function computeELStats(
|
||||
families: Famille[],
|
||||
currentDate: Date
|
||||
): ELStats {
|
||||
const actuelles = computeELStatsAtDate(families, currentDate);
|
||||
|
||||
const months = generateELMonths();
|
||||
const monthsStats = computeELPeriodStats(families, months);
|
||||
const yearsStats = computeELPeriodStats(families, generateELYears());
|
||||
|
||||
const monthsStats = computeELPeriodStats(families, generateELMonths());
|
||||
|
||||
return {
|
||||
actuelles: actuelles,
|
||||
|
|
|
|||
74
src/statistiques/computeELStatsAtDate.ts
Normal file
74
src/statistiques/computeELStatsAtDate.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import { isEvenementBefore, isProcedurePenale } 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";
|
||||
import { ELStatsAtDate } from "./ELStats";
|
||||
import { computePourcentageEntreeApresMiseEnDemeure } from "./computePourcentageEntreeApresMiseEnDemeure";
|
||||
|
||||
export function computeELStatsAtDate(
|
||||
familles: Famille[],
|
||||
asOfDate: Date
|
||||
): ELStatsAtDate<number> {
|
||||
const familleResistantes = familles.filter((f) => isResistant(f, asOfDate));
|
||||
const familleResistantesOrEx = familles.filter(
|
||||
(famille) =>
|
||||
isResistant(famille, asOfDate) || isExResistant(famille, asOfDate)
|
||||
);
|
||||
const dureesResistances = familles
|
||||
.map((famille) => dureeResistanceInDays(famille, asOfDate))
|
||||
.filter(notNull);
|
||||
|
||||
const nbFamillesMiseEnDemeure = familleResistantesOrEx.filter((f) =>
|
||||
f.Evenements.find((e) => e.Type === "Mise en demeure de scolarisation")
|
||||
).length;
|
||||
const pourcentageFamillesMisesEnDemeure = percent(
|
||||
nbFamillesMiseEnDemeure,
|
||||
familleResistantesOrEx.length
|
||||
);
|
||||
|
||||
const pourcentageEntreeApresMiseEnDemeure =
|
||||
computePourcentageEntreeApresMiseEnDemeure(familleResistantesOrEx);
|
||||
|
||||
const famillesAvecProcedurePenale = familleResistantesOrEx.filter((famille) =>
|
||||
famille.Evenements.find(
|
||||
(evt) => isProcedurePenale(evt) && isEvenementBefore(evt, asOfDate)
|
||||
)
|
||||
);
|
||||
const pourcentageFamillesProcedurePenale = percent(
|
||||
famillesAvecProcedurePenale.length,
|
||||
familleResistantesOrEx.length
|
||||
);
|
||||
const famillesAvecProcedureCivile = familleResistantesOrEx.filter((famille) =>
|
||||
famille.Evenements.find(
|
||||
(evt) => isProcedurePenale(evt) && isEvenementBefore(evt, asOfDate)
|
||||
)
|
||||
);
|
||||
const pourcentageFamillesProcedureCivile = percent(
|
||||
famillesAvecProcedureCivile.length,
|
||||
familleResistantesOrEx.length
|
||||
);
|
||||
|
||||
const actuelles: ELStatsAtDate<number> = {
|
||||
nbFamilleResistantes: familleResistantes.length,
|
||||
nbFamilleResistantesOrEx: familleResistantesOrEx.length,
|
||||
|
||||
dureeResistanceMoyenne: average(dureesResistances),
|
||||
dureeResistanceMediane: median(dureesResistances),
|
||||
|
||||
nbFamillesMisesEnDemeure: nbFamillesMiseEnDemeure,
|
||||
pourcentageFamillesMisesEnDemeure: pourcentageFamillesMisesEnDemeure,
|
||||
pourcentageEntreeApresMiseEnDemeure: pourcentageEntreeApresMiseEnDemeure,
|
||||
nbFamillesProcedurePenale: famillesAvecProcedurePenale.length,
|
||||
pourcentageFamillesProcedurePenale: pourcentageFamillesProcedurePenale,
|
||||
nbFamillesProcedureCivile: famillesAvecProcedureCivile.length,
|
||||
pourcentageFamillesProcedureCivile: pourcentageFamillesProcedureCivile,
|
||||
};
|
||||
return actuelles;
|
||||
}
|
||||
48
src/statistiques/computeELStatsOverPeriod.ts
Normal file
48
src/statistiques/computeELStatsOverPeriod.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import {
|
||||
isEvenementInPeriod,
|
||||
isProcedurePenale,
|
||||
} from "../data/EvenementFamille";
|
||||
import {
|
||||
Famille,
|
||||
isResistantOverPeriod,
|
||||
periodOfResistance,
|
||||
} from "../data/Famille";
|
||||
import { Period } from "../period/Period";
|
||||
import { isPeriodContaining } from "../period/isPeriodContaining";
|
||||
import { ELStatsOverPeriod } from "./ELStats";
|
||||
import { computePourcentageEntreeApresMiseEnDemeure } from "./computePourcentageEntreeApresMiseEnDemeure";
|
||||
|
||||
export function computeELStatsOverPeriod(
|
||||
familles: Famille[],
|
||||
period: Period
|
||||
): ELStatsOverPeriod<number> {
|
||||
const famillesEntreesSurPeriode = familles.filter((f) => {
|
||||
const por = periodOfResistance(f);
|
||||
return por != null && isPeriodContaining(period, por.start);
|
||||
});
|
||||
|
||||
const nbFamilleResistantesSurPeriode = familles.filter((famille) =>
|
||||
isResistantOverPeriod(famille, period)
|
||||
).length;
|
||||
const pourcentageEntreeApresMiseEnDemeureSurPeriode =
|
||||
computePourcentageEntreeApresMiseEnDemeure(famillesEntreesSurPeriode);
|
||||
const familleAvecProcedurePenaleSurPeriode = familles.filter((famille) =>
|
||||
famille.Evenements.find(
|
||||
(evt) => isProcedurePenale(evt) && isEvenementInPeriod(evt, period)
|
||||
)
|
||||
);
|
||||
const familleAvecProcedureCivileSurPeriode = familles.filter((famille) =>
|
||||
famille.Evenements.find(
|
||||
(evt) => isProcedurePenale(evt) && isEvenementInPeriod(evt, period)
|
||||
)
|
||||
);
|
||||
return {
|
||||
nbFamilleResistantesSurPeriode,
|
||||
pourcentageEntreeApresMiseEnDemeureSurPeriode,
|
||||
nbFamilleAvecProcedurePenaleSurPeriode:
|
||||
familleAvecProcedurePenaleSurPeriode.length,
|
||||
|
||||
nbFamilleAvecProcedureCivileSurPeriode:
|
||||
familleAvecProcedureCivileSurPeriode.length,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
import { Famille } from "../data/Famille";
|
||||
import { percent } from "../utils/math/percent";
|
||||
|
||||
export function computePourcentageEntreeApresMiseEnDemeure(
|
||||
familles: Famille[]
|
||||
) {
|
||||
const entreeApresMiseEnDemeure = familles.filter(
|
||||
(f) =>
|
||||
f.ContexteEntree === "Après mise en demeure" ||
|
||||
f.ContexteEntree === "Après poursuite procureur"
|
||||
);
|
||||
const pourcentageEntreeApresMiseEnDemeure = percent(
|
||||
entreeApresMiseEnDemeure.length,
|
||||
familles.length
|
||||
);
|
||||
return pourcentageEntreeApresMiseEnDemeure;
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
import {
|
||||
Famille,
|
||||
isExResistant,
|
||||
isResistant,
|
||||
periodOfResistance,
|
||||
} from "../data/Famille";
|
||||
import { daysInPeriod } from "../period/daysInPeriod";
|
||||
import { ELStatsActuelles } from "./ELStats";
|
||||
import { average } from "./math/average";
|
||||
import { median } from "./math/median";
|
||||
|
||||
export function computeStatsActuelles(familles: Famille[]): ELStatsActuelles {
|
||||
const resistantsCount = familles.filter((f) => isResistant(f)).length;
|
||||
const resistantsOrEx = familles.filter(
|
||||
(f) => isResistant(f) || isExResistant(f)
|
||||
);
|
||||
const durations = resistantsOrEx.map((f) =>
|
||||
daysInPeriod(periodOfResistance(f)!)
|
||||
);
|
||||
const dureeMoyenne = average(durations);
|
||||
const dureeMediane = median(durations);
|
||||
|
||||
const familleAvecMiseEnDemeure = resistantsOrEx.filter((f) =>
|
||||
f.Evenements.find((e) => e.Type === "Mise en demeure de scolarisation")
|
||||
);
|
||||
|
||||
const entreeApresMiseEnDemeure = resistantsOrEx.filter(
|
||||
(f) =>
|
||||
f.ContexteEntree === "Après mise en demeure" ||
|
||||
f.ContexteEntree === "Après poursuite procureur"
|
||||
);
|
||||
|
||||
const actuelles: ELStatsActuelles = {
|
||||
nbFamilleResistantes: resistantsCount,
|
||||
nbFamilleResistantesOrEx: resistantsOrEx.length,
|
||||
dureeResistanceMoyenne: dureeMoyenne,
|
||||
dureeResistanceMediane: dureeMediane,
|
||||
nbFamillesMisesEnDemeure: familleAvecMiseEnDemeure.length,
|
||||
pourcentageFamillesMisesEnDemeure:
|
||||
(100 * familleAvecMiseEnDemeure.length) / resistantsOrEx.length,
|
||||
pourcentageEntreeApresMiseEnDemeure:
|
||||
(100 * entreeApresMiseEnDemeure.length) / resistantsOrEx.length,
|
||||
};
|
||||
return actuelles;
|
||||
}
|
||||
3
src/utils/math/percent.ts
Normal file
3
src/utils/math/percent.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export function percent(value: number, total: number): number {
|
||||
return (100 * value) / total;
|
||||
}
|
||||
5
src/utils/notNull.ts
Normal file
5
src/utils/notNull.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
// Typescript is not able to infer this inline see
|
||||
// https://stackoverflow.com/questions/43118692/typescript-filter-out-nulls-from-an-array
|
||||
export function notNull<TValue>(value: TValue | null): value is TValue {
|
||||
return value !== null;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue