mirror of
https://framagit.org/enfance-libre/statistiques
synced 2025-04-26 15:23:46 +00:00
feat: ajout nbFicheIntegrationParStatuts + MultiValueStatDesc
This commit is contained in:
parent
f9f42bfbc4
commit
1d28784f74
12 changed files with 221 additions and 84 deletions
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
40
src/notion/publish/v2/createMultiValueStatListItemBlock.ts
Normal file
40
src/notion/publish/v2/createMultiValueStatListItemBlock.ts
Normal 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
|
||||
);
|
||||
});
|
||||
}
|
|
@ -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),
|
||||
},
|
||||
},
|
||||
],
|
|
@ -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";
|
||||
});
|
||||
}
|
||||
|
|
53
src/statistiques/v2/desc/StatsDesc copy.ts
Normal file
53
src/statistiques/v2/desc/StatsDesc copy.ts
Normal 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;
|
||||
};
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
16
yarn.lock
16
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue