From 1d28784f74384bd1f98f7cfaf3409a41ec4488e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Arod?= Date: Mon, 14 Oct 2024 15:51:24 +0200 Subject: [PATCH] feat: ajout nbFicheIntegrationParStatuts + MultiValueStatDesc --- package.json | 4 +- src/data/Famille.ts | 14 ++--- src/data/StatutFamille.ts | 19 ++++--- src/format/formatValue.ts | 11 ++-- .../v2/createMultiValueStatListItemBlock.ts | 40 ++++++++++++++ ... => createSingleValueStatListItemBlock.ts} | 12 ++--- .../v2/createStatGroupListItemBlock.ts | 33 +++++++++--- src/statistiques/v2/desc/StatsDesc copy.ts | 53 +++++++++++++++++++ src/statistiques/v2/desc/StatsDesc.ts | 45 +++++++++++----- .../v2/generales/StatsGenerales.ts | 27 +++------- .../v2/generales/computeStatsGenerales.ts | 31 +++++------ yarn.lock | 16 ++++++ 12 files changed, 221 insertions(+), 84 deletions(-) create mode 100644 src/notion/publish/v2/createMultiValueStatListItemBlock.ts rename src/notion/publish/v2/{createStatListItemBlock.ts => createSingleValueStatListItemBlock.ts} (55%) create mode 100644 src/statistiques/v2/desc/StatsDesc copy.ts diff --git a/package.json b/package.json index fd7ae12..5c31fd3 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/data/Famille.ts b/src/data/Famille.ts index 20cfd4f..b6c7bf4 100644 --- a/src/data/Famille.ts +++ b/src/data/Famille.ts @@ -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 ); } diff --git a/src/data/StatutFamille.ts b/src/data/StatutFamille.ts index f693400..8558f51 100644 --- a/src/data/StatutFamille.ts +++ b/src/data/StatutFamille.ts @@ -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]; diff --git a/src/format/formatValue.ts b/src/format/formatValue.ts index 4a3e36f..0214cc4 100644 --- a/src/format/formatValue.ts +++ b/src/format/formatValue.ts @@ -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; } diff --git a/src/notion/publish/v2/createMultiValueStatListItemBlock.ts b/src/notion/publish/v2/createMultiValueStatListItemBlock.ts new file mode 100644 index 0000000..4029131 --- /dev/null +++ b/src/notion/publish/v2/createMultiValueStatListItemBlock.ts @@ -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 +): BulletedListItemBlockObjectRequest { + return { + bulleted_list_item: { + rich_text: [ + { + text: { + content: descriptor.label, + }, + }, + ], + children: createMultiValueStatListItemBlockChildren( + descriptor, + statValue + ) as BulletedListItemChildren, + }, + }; +} + +function createMultiValueStatListItemBlockChildren( + formatOptions: ValueFormatOptions, + statsValues: Record +): BulletedListItemBlockObjectRequest[] { + return Object.keys(statsValues).map((keyName) => { + const statValue = statsValues[keyName]; + return createSingleValueStatListItemBlock( + keyName, + formatOptions, + statValue + ); + }); +} diff --git a/src/notion/publish/v2/createStatListItemBlock.ts b/src/notion/publish/v2/createSingleValueStatListItemBlock.ts similarity index 55% rename from src/notion/publish/v2/createStatListItemBlock.ts rename to src/notion/publish/v2/createSingleValueStatListItemBlock.ts index a9ee702..81401e7 100644 --- a/src/notion/publish/v2/createStatListItemBlock.ts +++ b/src/notion/publish/v2/createSingleValueStatListItemBlock.ts @@ -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), }, }, ], diff --git a/src/notion/publish/v2/createStatGroupListItemBlock.ts b/src/notion/publish/v2/createStatGroupListItemBlock.ts index fe29025..ac3a390 100644 --- a/src/notion/publish/v2/createStatGroupListItemBlock.ts +++ b/src/notion/publish/v2/createStatGroupListItemBlock.ts @@ -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( descriptor: D, @@ -36,11 +40,26 @@ export function createStatGroupChildrenListItemBlock( const childStatDesc = descriptor.stats[statName]; const childStatValue = stats[statName]; - return isStatGroupDesc(childStatDesc) - ? createStatGroupListItemBlock( - childStatDesc, - childStatValue as StatsType - ) - : createStatListItemBlock(childStatDesc, childStatValue as number); + if (isStatGroupDesc(childStatDesc)) { + return createStatGroupListItemBlock( + childStatDesc, + childStatValue as StatsType + ); + } + if (isSingleValueStatDesc(childStatDesc)) { + return createSingleValueStatListItemBlock( + childStatDesc.label, + childStatDesc as ValueFormatOptions, + childStatValue as number + ); + } + + if (isMultiValueStatDesc(childStatDesc)) { + return createMultiValueStatListItemBlock( + childStatDesc, + childStatValue as Record + ); + } + throw "Mussing case"; }); } diff --git a/src/statistiques/v2/desc/StatsDesc copy.ts b/src/statistiques/v2/desc/StatsDesc copy.ts new file mode 100644 index 0000000..d265c3e --- /dev/null +++ b/src/statistiques/v2/desc/StatsDesc copy.ts @@ -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 = { + [Property in keyof T["stats"]]: T["stats"][Property] extends StatGroupDesc + ? StatsType + : T["stats"][Property] extends MultiValueStatDesc + ? Record + : number; +}; diff --git a/src/statistiques/v2/desc/StatsDesc.ts b/src/statistiques/v2/desc/StatsDesc.ts index 8629bdd..7026daa 100644 --- a/src/statistiques/v2/desc/StatsDesc.ts +++ b/src/statistiques/v2/desc/StatsDesc.ts @@ -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 = { [Property in keyof T["stats"]]: T["stats"][Property] extends StatGroupDesc ? StatsType + : T["stats"][Property] extends MultiValueStatDesc + ? Record : number; }; diff --git a/src/statistiques/v2/generales/StatsGenerales.ts b/src/statistiques/v2/generales/StatsGenerales.ts index 55b0871..cf2f1d6 100644 --- a/src/statistiques/v2/generales/StatsGenerales.ts +++ b/src/statistiques/v2/generales/StatsGenerales.ts @@ -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; diff --git a/src/statistiques/v2/generales/computeStatsGenerales.ts b/src/statistiques/v2/generales/computeStatsGenerales.ts index c047306..a493150 100644 --- a/src/statistiques/v2/generales/computeStatsGenerales.ts +++ b/src/statistiques/v2/generales/computeStatsGenerales.ts @@ -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; } diff --git a/yarn.lock b/yarn.lock index c9211eb..090b5af 100644 --- a/yarn.lock +++ b/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"