feat: ajout nbFicheIntegrationParStatuts + MultiValueStatDesc

This commit is contained in:
Sébastien Arod 2024-10-14 15:51:24 +02:00
parent f9f42bfbc4
commit 1d28784f74
12 changed files with 221 additions and 84 deletions

View file

@ -3,13 +3,15 @@
"packageManager": "yarn@4.2.2", "packageManager": "yarn@4.2.2",
"dependencies": { "dependencies": {
"@notionhq/client": "^2.2.14", "@notionhq/client": "^2.2.14",
"date-fns": "^3.6.0" "date-fns": "^3.6.0",
"lodash": "^4.17.21"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.3.0", "@eslint/js": "^9.3.0",
"@jest/globals": "^29.7.0", "@jest/globals": "^29.7.0",
"@swc/core": "^1.5.24", "@swc/core": "^1.5.24",
"@swc/jest": "^0.2.36", "@swc/jest": "^0.2.36",
"@types/lodash": "^4",
"@types/node": "^20.12.12", "@types/node": "^20.12.12",
"eslint": "9.x", "eslint": "9.x",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",

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 {
StatutFamille,
StatutIntegrationEnCours,
statutsIntegrationEnCours,
} from "./StatutFamille";
import { StatutSocial } from "./StatutSocial"; import { StatutSocial } from "./StatutSocial";
import { StatutPenal } from "./StatutPenal"; import { StatutPenal } from "./StatutPenal";
@ -48,12 +52,8 @@ export function isResistant(
} }
export function isIntegration(famille: Famille) { export function isIntegration(famille: Famille) {
return ( return statutsIntegrationEnCours.includes(
famille.Statut === "Se questionne" || famille.Statut as StatutIntegrationEnCours
famille.Statut === "Déclaration validée - Attente éléments" ||
famille.Statut === "Rédaction Déclaration" ||
famille.Statut === "Désobéissance décidée" ||
famille.Statut === "en réflexion"
); );
} }

View file

@ -1,13 +1,18 @@
export const statutsIntegrationEnCours = [
"en réflexion",
"Intégration à finaliser (vérification de la fiche))",
"Intégration en cours",
] as const;
export const statutsIntegrationEnEchec = ["Abandon", "Incompatible"] as const;
export const statutsFamille = [ export const statutsFamille = [
"Résistant.e", "Résistant.e",
"Ex résistant·e·s", "Ex résistant·e·s",
"en réflexion", ...statutsIntegrationEnCours,
"Se questionne", ...statutsIntegrationEnEchec,
"Désobéissance décidée",
"Rédaction Déclaration",
"Déclaration validée - Attente éléments",
"Abandon",
"Incompatible",
] as const; ] as const;
export type StatutIntegrationEnCours =
(typeof statutsIntegrationEnCours)[number];
export type StatutFamille = (typeof statutsFamille)[number]; export type StatutFamille = (typeof statutsFamille)[number];

View file

@ -1,15 +1,18 @@
import { ValueFormatOptions } from "./ValueFormatOptions"; import { ValueFormatOptions } from "./ValueFormatOptions";
export function formatValue(value: number, publishOptions: ValueFormatOptions) { export function formatValue(
value: number,
valueFormatOptions: ValueFormatOptions
) {
const valueStr = value.toLocaleString("fr-FR", { const valueStr = value.toLocaleString("fr-FR", {
useGrouping: false, useGrouping: false,
maximumFractionDigits: maximumFractionDigits:
publishOptions.valueMaxFractioDigits === undefined valueFormatOptions.valueMaxFractioDigits === undefined
? 1 ? 1
: publishOptions.valueMaxFractioDigits, : valueFormatOptions.valueMaxFractioDigits,
}); });
const formattedValue = `${valueStr}${ const formattedValue = `${valueStr}${
publishOptions.unit === undefined ? "" : publishOptions.unit valueFormatOptions.unit === undefined ? "" : valueFormatOptions.unit
}`; }`;
return formattedValue; return formattedValue;
} }

View file

@ -0,0 +1,40 @@
import { ValueFormatOptions } from "../../../format/ValueFormatOptions";
import { MultiValueStatDesc } from "../../../statistiques/v2/desc/StatsDesc";
import { BulletedListItemBlockObjectRequest } from "../blocks/BulletedListItemBlockObjectRequest";
import { BulletedListItemChildren } from "../blocks/BulletedListItemChildren";
import { createSingleValueStatListItemBlock } from "./createSingleValueStatListItemBlock";
export function createMultiValueStatListItemBlock(
descriptor: MultiValueStatDesc,
statValue: Record<string, number>
): BulletedListItemBlockObjectRequest {
return {
bulleted_list_item: {
rich_text: [
{
text: {
content: descriptor.label,
},
},
],
children: createMultiValueStatListItemBlockChildren(
descriptor,
statValue
) as BulletedListItemChildren,
},
};
}
function createMultiValueStatListItemBlockChildren(
formatOptions: ValueFormatOptions,
statsValues: Record<string, number>
): BulletedListItemBlockObjectRequest[] {
return Object.keys(statsValues).map((keyName) => {
const statValue = statsValues[keyName];
return createSingleValueStatListItemBlock(
keyName,
formatOptions,
statValue
);
});
}

View file

@ -1,18 +1,18 @@
import { formatValue } from "../../../format/formatValue"; import { formatValue } from "../../../format/formatValue";
import { StatDesc } from "../../../statistiques/v2/desc/StatsDesc";
import { BulletedListItemBlockObjectRequest } from "../blocks/BulletedListItemBlockObjectRequest"; import { BulletedListItemBlockObjectRequest } from "../blocks/BulletedListItemBlockObjectRequest";
import { ValueFormatOptions } from "../../../format/ValueFormatOptions";
export function createStatListItemBlock( export function createSingleValueStatListItemBlock(
descriptor: StatDesc, label: string,
statValue: number formatOptions: ValueFormatOptions,
value: number
): BulletedListItemBlockObjectRequest { ): BulletedListItemBlockObjectRequest {
return { return {
bulleted_list_item: { bulleted_list_item: {
rich_text: [ rich_text: [
{ {
text: { text: {
content: content: label + ": " + formatValue(value, formatOptions),
descriptor.label + ": " + formatValue(statValue, descriptor),
}, },
}, },
], ],

View file

@ -1,11 +1,15 @@
import { import {
isMultiValueStatDesc,
isSingleValueStatDesc,
isStatGroupDesc, isStatGroupDesc,
StatGroupDesc, StatGroupDesc,
StatsType, StatsType,
} from "../../../statistiques/v2/desc/StatsDesc"; } from "../../../statistiques/v2/desc/StatsDesc";
import { createStatListItemBlock } from "./createStatListItemBlock"; import { createSingleValueStatListItemBlock } from "./createSingleValueStatListItemBlock";
import { BulletedListItemBlockObjectRequest } from "../blocks/BulletedListItemBlockObjectRequest"; import { BulletedListItemBlockObjectRequest } from "../blocks/BulletedListItemBlockObjectRequest";
import { BulletedListItemChildren } from "../blocks/BulletedListItemChildren"; import { BulletedListItemChildren } from "../blocks/BulletedListItemChildren";
import { createMultiValueStatListItemBlock } from "./createMultiValueStatListItemBlock";
import { ValueFormatOptions } from "../../../format/ValueFormatOptions";
export function createStatGroupListItemBlock<D extends StatGroupDesc>( export function createStatGroupListItemBlock<D extends StatGroupDesc>(
descriptor: D, descriptor: D,
@ -36,11 +40,26 @@ export function createStatGroupChildrenListItemBlock<D extends StatGroupDesc>(
const childStatDesc = descriptor.stats[statName]; const childStatDesc = descriptor.stats[statName];
const childStatValue = stats[statName]; const childStatValue = stats[statName];
return isStatGroupDesc(childStatDesc) if (isStatGroupDesc(childStatDesc)) {
? createStatGroupListItemBlock( return createStatGroupListItemBlock(
childStatDesc, childStatDesc,
childStatValue as StatsType<typeof childStatDesc> childStatValue as StatsType<typeof childStatDesc>
) );
: createStatListItemBlock(childStatDesc, childStatValue as number); }
if (isSingleValueStatDesc(childStatDesc)) {
return createSingleValueStatListItemBlock(
childStatDesc.label,
childStatDesc as ValueFormatOptions,
childStatValue as number
);
}
if (isMultiValueStatDesc(childStatDesc)) {
return createMultiValueStatListItemBlock(
childStatDesc,
childStatValue as Record<string, number>
);
}
throw "Mussing case";
}); });
} }

View file

@ -0,0 +1,53 @@
import { ValueFormatOptions } from "../../../format/ValueFormatOptions";
type StatDescTypes = StatGroupDesc | StatDesc | MultiValueStatDesc;
/**
* Descripteur d'une Section de stat
*/
export type StatGroupDesc = {
label: string;
desc?: string;
stats: { [propName: string]: StatDescTypes };
};
export function isStatGroupDesc(x: StatDescTypes): x is StatGroupDesc {
if (!("stats" in x) || x.stats === null || typeof x.stats !== "object") {
return false;
}
return true;
}
/**
* Descripteur d'une valeur unique
*/
export type StatDesc = {
label: string;
} & ValueFormatOptions;
export function isSingleValueStatDesc(x: StatDescTypes): x is StatDesc {
return !isStatGroupDesc(x) && !isMultiValueStatDesc(x);
}
export type MultiValueStatDesc = {
label: string;
type: "multi";
} & ValueFormatOptions;
export function isMultiValueStatDesc(
x: StatDescTypes
): x is MultiValueStatDesc {
if (!("label" in x) || x.label === null || typeof x.label !== "string") {
return false;
}
return "type" in x && x.type === "multi";
}
export type StatsType<T extends StatGroupDesc> = {
[Property in keyof T["stats"]]: T["stats"][Property] extends StatGroupDesc
? StatsType<T["stats"][Property]>
: T["stats"][Property] extends MultiValueStatDesc
? Record<string, number>
: number;
};

View file

@ -1,23 +1,17 @@
import { ValueFormatOptions } from "../../../format/ValueFormatOptions"; import { ValueFormatOptions } from "../../../format/ValueFormatOptions";
type StatDesc = StatGroupDesc | SingleValueStatDesc | MultiValueStatDesc;
/**
* Descripteur d'une Section de stat
*/
export type StatGroupDesc = { export type StatGroupDesc = {
label: string; label: string;
desc?: string; desc?: string;
stats: { [propName: string]: StatDesc | StatGroupDesc }; stats: { [propName: string]: StatDesc };
}; };
export type StatDesc = { export function isStatGroupDesc(x: StatDesc): x is StatGroupDesc {
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") { if (!("stats" in x) || x.stats === null || typeof x.stats !== "object") {
return false; return false;
} }
@ -25,8 +19,33 @@ export function isStatGroupDesc(x: unknown): x is StatGroupDesc {
return true; return true;
} }
/**
* Descripteur d'une valeur unique
*/
export type SingleValueStatDesc = {
label: string;
} & ValueFormatOptions;
export function isSingleValueStatDesc(x: StatDesc): x is SingleValueStatDesc {
return !isStatGroupDesc(x) && !isMultiValueStatDesc(x);
}
export type MultiValueStatDesc = {
label: string;
type: "multi";
} & ValueFormatOptions;
export function isMultiValueStatDesc(x: StatDesc): x is MultiValueStatDesc {
if (!("label" in x) || x.label === null || typeof x.label !== "string") {
return false;
}
return "type" in x && x.type === "multi";
}
export type StatsType<T extends StatGroupDesc> = { export type StatsType<T extends StatGroupDesc> = {
[Property in keyof T["stats"]]: T["stats"][Property] extends StatGroupDesc [Property in keyof T["stats"]]: T["stats"][Property] extends StatGroupDesc
? StatsType<T["stats"][Property]> ? StatsType<T["stats"][Property]>
: T["stats"][Property] extends MultiValueStatDesc
? Record<string, number>
: number; : number;
}; };

View file

@ -17,30 +17,17 @@ export const statsGeneralesDesc = {
label: "Durée médiane de résistance", label: "Durée médiane de résistance",
unit: " jours", unit: " jours",
}, },
contexteEntree: { nbFamillesParContexteDEntree: {
label: "Contexte d'entrée des familles", label: "Nb Familles par contexte d'entrée",
stats: { type: "multi",
pasDeDemandePleinDroit: {
label: "Pas de demande (Plein droit)",
},
pasDeDemande: {
label: "Pas de demande",
},
apresRefus: {
label: "Après refus",
},
apresMiseEnDemeure: {
label: "Après mise en demeure",
},
apresPoursuiteProcureur: {
label: "Après poursuite procureur",
},
},
}, },
nbFicheIntegrationActiviteRecente: { nbFicheIntegrationActiviteRecente: {
label: "Nb fiche d'intégration avec une activité < 30j", label: "Nb fiche d'intégration avec une activité < 30j",
}, },
nbFicheIntegrationParStatuts: {
label: "Nb fiche d'intégration par statuts",
type: "multi",
},
}, },
} as const; } as const;

View file

@ -9,11 +9,14 @@ import {
import { average } from "../../../utils/math/average"; import { average } from "../../../utils/math/average";
import { median } from "../../../utils/math/median"; import { median } from "../../../utils/math/median";
import { StatsGenerales } from "./StatsGenerales"; import { StatsGenerales } from "./StatsGenerales";
import { countBy } from "lodash";
export function computeStatsGenerales(familles: Famille[]): StatsGenerales { export function computeStatsGenerales(familles: Famille[]): StatsGenerales {
const famillesResistantesOrEx = familles.filter( const famillesResistantesOrEx = familles.filter(
(f) => isResistant(f) || isExResistant(f) (f) => isResistant(f) || isExResistant(f)
); );
const famillesresistantes = familles.filter((f) => isResistant(f));
const dureesResistances = famillesResistantesOrEx.map( const dureesResistances = famillesResistantesOrEx.map(
(f) => dureeResistanceInDays(f)! (f) => dureeResistanceInDays(f)!
); );
@ -23,31 +26,21 @@ export function computeStatsGenerales(familles: Famille[]): StatsGenerales {
isIntegration(f) && differenceInDays(now, f.DerniereModification) <= 30 isIntegration(f) && differenceInDays(now, f.DerniereModification) <= 30
); );
const statsGenerales: StatsGenerales = { const statsGenerales: StatsGenerales = {
nbFamillesResistantesActuelles: familles.filter((f) => isResistant(f)) nbFamillesResistantesActuelles: famillesresistantes.length,
.length,
nbFamillesResistantesActuellesOuPassees: famillesResistantesOrEx.length, nbFamillesResistantesActuellesOuPassees: famillesResistantesOrEx.length,
dureeResistanceMedianne: median(dureesResistances), dureeResistanceMedianne: median(dureesResistances),
dureeResistanceMoyenne: average(dureesResistances), dureeResistanceMoyenne: average(dureesResistances),
contexteEntree: { nbFamillesParContexteDEntree: countBy(
pasDeDemandePleinDroit: famillesResistantesOrEx.filter( famillesResistantesOrEx,
(f) => f.ContexteEntree === "Pas de demande (Plein droit)" (f) => f.ContexteEntree
).length, ),
pasDeDemande: famillesResistantesOrEx.filter(
(f) => f.ContexteEntree === "Pas de demande"
).length,
apresRefus: famillesResistantesOrEx.filter(
(f) => f.ContexteEntree === "Après refus"
).length,
apresMiseEnDemeure: famillesResistantesOrEx.filter(
(f) => f.ContexteEntree === "Après mise en demeure"
).length,
apresPoursuiteProcureur: famillesResistantesOrEx.filter(
(f) => f.ContexteEntree === "Après poursuite procureur"
).length,
},
nbFicheIntegrationActiviteRecente: nbFicheIntegrationActiviteRecente:
fichesIntegrationRecementModifiees.length, fichesIntegrationRecementModifiees.length,
nbFicheIntegrationParStatuts: countBy(
familles.filter((f) => isIntegration(f)),
(f) => f.Statut
),
}; };
return statsGenerales; return statsGenerales;
} }

View file

@ -1155,6 +1155,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@types/lodash@npm:^4":
version: 4.17.10
resolution: "@types/lodash@npm:4.17.10"
checksum: 10c0/149b2b9fcc277204393423ed14df28894980c2322ec522fc23f2c6f7edef6ee8d876ee09ed4520f45d128adc0a7a6e618bb0017668349716cd99c6ef54a21621
languageName: node
linkType: hard
"@types/node-fetch@npm:^2.5.10": "@types/node-fetch@npm:^2.5.10":
version: 2.6.11 version: 2.6.11
resolution: "@types/node-fetch@npm:2.6.11" resolution: "@types/node-fetch@npm:2.6.11"
@ -3571,6 +3578,13 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"lodash@npm:^4.17.21":
version: 4.17.21
resolution: "lodash@npm:4.17.21"
checksum: 10c0/d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c
languageName: node
linkType: hard
"log-update@npm:^6.0.0": "log-update@npm:^6.0.0":
version: 6.0.0 version: 6.0.0
resolution: "log-update@npm:6.0.0" resolution: "log-update@npm:6.0.0"
@ -4487,6 +4501,7 @@ __metadata:
"@notionhq/client": "npm:^2.2.14" "@notionhq/client": "npm:^2.2.14"
"@swc/core": "npm:^1.5.24" "@swc/core": "npm:^1.5.24"
"@swc/jest": "npm:^0.2.36" "@swc/jest": "npm:^0.2.36"
"@types/lodash": "npm:^4"
"@types/node": "npm:^20.12.12" "@types/node": "npm:^20.12.12"
date-fns: "npm:^3.6.0" date-fns: "npm:^3.6.0"
eslint: "npm:9.x" eslint: "npm:9.x"
@ -4495,6 +4510,7 @@ __metadata:
husky: "npm:^9.0.11" husky: "npm:^9.0.11"
jest: "npm:^29.7.0" jest: "npm:^29.7.0"
lint-staged: "npm:^15.2.5" lint-staged: "npm:^15.2.5"
lodash: "npm:^4.17.21"
prettier: "npm:^2.8.4" prettier: "npm:^2.8.4"
ts-node: "npm:^10.9.2" ts-node: "npm:^10.9.2"
typescript: "npm:^5.4.5" typescript: "npm:^5.4.5"