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",
"dependencies": {
"@notionhq/client": "^2.2.14",
"date-fns": "^3.6.0"
"date-fns": "^3.6.0",
"lodash": "^4.17.21"
},
"devDependencies": {
"@eslint/js": "^9.3.0",
"@jest/globals": "^29.7.0",
"@swc/core": "^1.5.24",
"@swc/jest": "^0.2.36",
"@types/lodash": "^4",
"@types/node": "^20.12.12",
"eslint": "9.x",
"eslint-config-prettier": "^9.1.0",

View file

@ -4,7 +4,11 @@ import { arePeriodsOverlaping } from "../period/arePeriodsOverlaping";
import { isPeriodContaining } from "../period/isPeriodContaining";
import { ContexteEntreeDC } from "./ContexteEntreeDC";
import { EvenementFamille } from "./EvenementFamille";
import { StatutFamille } from "./StatutFamille";
import {
StatutFamille,
StatutIntegrationEnCours,
statutsIntegrationEnCours,
} from "./StatutFamille";
import { StatutSocial } from "./StatutSocial";
import { StatutPenal } from "./StatutPenal";
@ -48,12 +52,8 @@ export function isResistant(
}
export function isIntegration(famille: Famille) {
return (
famille.Statut === "Se questionne" ||
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"
return statutsIntegrationEnCours.includes(
famille.Statut as StatutIntegrationEnCours
);
}

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 = [
"Résistant.e",
"Ex résistant·e·s",
"en réflexion",
"Se questionne",
"Désobéissance décidée",
"Rédaction Déclaration",
"Déclaration validée - Attente éléments",
"Abandon",
"Incompatible",
...statutsIntegrationEnCours,
...statutsIntegrationEnEchec,
] as const;
export type StatutIntegrationEnCours =
(typeof statutsIntegrationEnCours)[number];
export type StatutFamille = (typeof statutsFamille)[number];

View file

@ -1,15 +1,18 @@
import { ValueFormatOptions } from "./ValueFormatOptions";
export function formatValue(value: number, publishOptions: ValueFormatOptions) {
export function formatValue(
value: number,
valueFormatOptions: ValueFormatOptions
) {
const valueStr = value.toLocaleString("fr-FR", {
useGrouping: false,
maximumFractionDigits:
publishOptions.valueMaxFractioDigits === undefined
valueFormatOptions.valueMaxFractioDigits === undefined
? 1
: publishOptions.valueMaxFractioDigits,
: valueFormatOptions.valueMaxFractioDigits,
});
const formattedValue = `${valueStr}${
publishOptions.unit === undefined ? "" : publishOptions.unit
valueFormatOptions.unit === undefined ? "" : valueFormatOptions.unit
}`;
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 { StatDesc } from "../../../statistiques/v2/desc/StatsDesc";
import { BulletedListItemBlockObjectRequest } from "../blocks/BulletedListItemBlockObjectRequest";
import { ValueFormatOptions } from "../../../format/ValueFormatOptions";
export function createStatListItemBlock(
descriptor: StatDesc,
statValue: number
export function createSingleValueStatListItemBlock(
label: string,
formatOptions: ValueFormatOptions,
value: number
): BulletedListItemBlockObjectRequest {
return {
bulleted_list_item: {
rich_text: [
{
text: {
content:
descriptor.label + ": " + formatValue(statValue, descriptor),
content: label + ": " + formatValue(value, formatOptions),
},
},
],

View file

@ -1,11 +1,15 @@
import {
isMultiValueStatDesc,
isSingleValueStatDesc,
isStatGroupDesc,
StatGroupDesc,
StatsType,
} from "../../../statistiques/v2/desc/StatsDesc";
import { createStatListItemBlock } from "./createStatListItemBlock";
import { createSingleValueStatListItemBlock } from "./createSingleValueStatListItemBlock";
import { BulletedListItemBlockObjectRequest } from "../blocks/BulletedListItemBlockObjectRequest";
import { BulletedListItemChildren } from "../blocks/BulletedListItemChildren";
import { createMultiValueStatListItemBlock } from "./createMultiValueStatListItemBlock";
import { ValueFormatOptions } from "../../../format/ValueFormatOptions";
export function createStatGroupListItemBlock<D extends StatGroupDesc>(
descriptor: D,
@ -36,11 +40,26 @@ export function createStatGroupChildrenListItemBlock<D extends StatGroupDesc>(
const childStatDesc = descriptor.stats[statName];
const childStatValue = stats[statName];
return isStatGroupDesc(childStatDesc)
? createStatGroupListItemBlock(
childStatDesc,
childStatValue as StatsType<typeof childStatDesc>
)
: createStatListItemBlock(childStatDesc, childStatValue as number);
if (isStatGroupDesc(childStatDesc)) {
return createStatGroupListItemBlock(
childStatDesc,
childStatValue as StatsType<typeof childStatDesc>
);
}
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";
type StatDesc = StatGroupDesc | SingleValueStatDesc | MultiValueStatDesc;
/**
* Descripteur d'une Section de stat
*/
export type StatGroupDesc = {
label: string;
desc?: string;
stats: { [propName: string]: StatDesc | StatGroupDesc };
stats: { [propName: string]: StatDesc };
};
export type StatDesc = {
label: string;
} & ValueFormatOptions;
export function isStatGroupDesc(x: unknown): x is StatGroupDesc {
if (typeof x !== "object" || Array.isArray(x) || x === null) {
return false;
}
if (!("label" in x) || x.label === null || typeof x.label !== "string") {
return false;
}
export function isStatGroupDesc(x: StatDesc): x is StatGroupDesc {
if (!("stats" in x) || x.stats === null || typeof x.stats !== "object") {
return false;
}
@ -25,8 +19,33 @@ export function isStatGroupDesc(x: unknown): x is StatGroupDesc {
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> = {
[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

@ -17,30 +17,17 @@ export const statsGeneralesDesc = {
label: "Durée médiane de résistance",
unit: " jours",
},
contexteEntree: {
label: "Contexte d'entrée des familles",
stats: {
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",
},
},
nbFamillesParContexteDEntree: {
label: "Nb Familles par contexte d'entrée",
type: "multi",
},
nbFicheIntegrationActiviteRecente: {
label: "Nb fiche d'intégration avec une activité < 30j",
},
nbFicheIntegrationParStatuts: {
label: "Nb fiche d'intégration par statuts",
type: "multi",
},
},
} as const;

View file

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

View file

@ -1155,6 +1155,13 @@ __metadata:
languageName: node
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":
version: 2.6.11
resolution: "@types/node-fetch@npm:2.6.11"
@ -3571,6 +3578,13 @@ __metadata:
languageName: node
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":
version: 6.0.0
resolution: "log-update@npm:6.0.0"
@ -4487,6 +4501,7 @@ __metadata:
"@notionhq/client": "npm:^2.2.14"
"@swc/core": "npm:^1.5.24"
"@swc/jest": "npm:^0.2.36"
"@types/lodash": "npm:^4"
"@types/node": "npm:^20.12.12"
date-fns: "npm:^3.6.0"
eslint: "npm:9.x"
@ -4495,6 +4510,7 @@ __metadata:
husky: "npm:^9.0.11"
jest: "npm:^29.7.0"
lint-staged: "npm:^15.2.5"
lodash: "npm:^4.17.21"
prettier: "npm:^2.8.4"
ts-node: "npm:^10.9.2"
typescript: "npm:^5.4.5"