diff --git a/src/index.ts b/src/index.ts index 5b7bd95..63cc768 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,13 +8,15 @@ import { computeELStats } from "./statistiques/v1/computeELStats"; import { computeStatsPenales } from "./statistiques/v2/penales/computeStatsPenales"; import { statsPenalesDesc } from "./statistiques/v2/penales/StatsPenales"; import { computeStatsGenerales } from "./statistiques/v2/generales/computeStatsGenerales"; -import { statsGeneralesDesc } from "./statistiques/v2/generales/StatsGenerales"; import { computeStatsSociales } from "./statistiques/v2/sociales/computeStatsSociales"; import { statsSocialesDesc } from "./statistiques/v2/sociales/StatsSociales"; import { checkDbSchemas } from "./notion/fetch/schemaCheck/checkDbSchemas"; import { computeSequencEvtPenalSankeyData } from "./statistiques/v2/penales/computeSankeyData"; import { sankeyDataToMermaidDiagram } from "./statistiques/v2/sankey/sankeyDataToMermaidDiagram"; import { formatDate } from "date-fns"; +import { computeStatsGeneralesMensuelles } from "./statistiques/v2/generales/computeStatsGeneralesMensuelles"; +import { mermaidDiagramStatsGeneralesMensuelles } from "./statistiques/v2/generales/mermaidDiagramStatsGeneralesMensuelles"; +import { publishStatsGenerales } from "./notion/publish/v2/publishStatsGenerales"; type ProcessOptions = { dryRun: boolean; @@ -75,13 +77,20 @@ function buildProcessOptions(): ProcessOptions { const elStats = computeELStats(familles, currentDate); const statsGenerales = computeStatsGenerales(familles); + const statsPenales = computeStatsPenales(familles); const statsSociales = computeStatsSociales(familles); - + const statsGeneralesMensuelles = computeStatsGeneralesMensuelles(familles); + const mermaidDiagramStatsGeneralesMensuellesCode = + mermaidDiagramStatsGeneralesMensuelles(statsGeneralesMensuelles); writeFileSync( "./el-stats-penal-sankey-diagram.txt", sankeyDataToMermaidDiagram(computeSequencEvtPenalSankeyData(familles)) ); + writeFileSync( + "./el-stats-stats-generales-mensuel.txt", + mermaidDiagramStatsGeneralesMensuellesCode + ); if (options.dryRun) { console.log( "Dry run => Skip Publishing. Stats are dumped in file el-stats-xxx.json" @@ -95,6 +104,7 @@ function buildProcessOptions(): ProcessOptions { generales: statsGenerales, penales: statsPenales, sociales: statsSociales, + StatsGeneralesMensuelles: statsGeneralesMensuelles, }, null, " " @@ -109,12 +119,11 @@ function buildProcessOptions(): ProcessOptions { Dernière mise à jour le ${formatDate(currentDate, "dd/MM/yyyy à HH:mm")} `; - await publishStatsToPage( + await publishStatsGenerales( notionClient, - "313751fb-daed-4b33-992f-c86d7ac2de37", header, - statsGeneralesDesc, - statsGenerales + statsGenerales, + mermaidDiagramStatsGeneralesMensuellesCode ); await publishStatsToPage( notionClient, diff --git a/src/notion/publish/blocks/BulletedListItemBlockObjectRequest.ts b/src/notion/publish/blocks/BulletedListItemBlockObjectRequest.ts new file mode 100644 index 0000000..f6357b5 --- /dev/null +++ b/src/notion/publish/blocks/BulletedListItemBlockObjectRequest.ts @@ -0,0 +1,6 @@ +import { BlockObjectRequest } from "@notionhq/client/build/src/api-endpoints"; + +export type BulletedListItemBlockObjectRequest = Extract< + BlockObjectRequest, + { bulleted_list_item: object } +>; diff --git a/src/notion/publish/blocks/BulletedListItemChildren.ts b/src/notion/publish/blocks/BulletedListItemChildren.ts new file mode 100644 index 0000000..732f6d2 --- /dev/null +++ b/src/notion/publish/blocks/BulletedListItemChildren.ts @@ -0,0 +1,4 @@ +import { BulletedListItemBlockObjectRequest } from "./BulletedListItemBlockObjectRequest"; + +export type BulletedListItemChildren = + BulletedListItemBlockObjectRequest["bulleted_list_item"]["children"]; diff --git a/src/notion/publish/blocks/ParagraphBlockObjectRequest.ts b/src/notion/publish/blocks/ParagraphBlockObjectRequest.ts new file mode 100644 index 0000000..8a3de69 --- /dev/null +++ b/src/notion/publish/blocks/ParagraphBlockObjectRequest.ts @@ -0,0 +1,6 @@ +import { BlockObjectRequest } from "@notionhq/client/build/src/api-endpoints"; + +export type ParagraphBlockObjectRequest = Extract< + BlockObjectRequest, + { paragraph: unknown } +>; diff --git a/src/notion/publish/blocks/createMermaidCodeBlock.ts b/src/notion/publish/blocks/createMermaidCodeBlock.ts new file mode 100644 index 0000000..2a652ee --- /dev/null +++ b/src/notion/publish/blocks/createMermaidCodeBlock.ts @@ -0,0 +1,22 @@ +import { BlockObjectRequest } from "@notionhq/client/build/src/api-endpoints"; + +export function createMermaidCodeBlock(code: string): CodeBlockObjectRequest { + return { + code: { + language: "mermaid", + rich_text: [ + { + type: "text", + text: { + content: code, + }, + }, + ], + }, + }; +} + +export type CodeBlockObjectRequest = Extract< + BlockObjectRequest, + { code: unknown } +>; diff --git a/src/notion/publish/blocks/createParagraphBlock.ts b/src/notion/publish/blocks/createParagraphBlock.ts new file mode 100644 index 0000000..e9b4c6c --- /dev/null +++ b/src/notion/publish/blocks/createParagraphBlock.ts @@ -0,0 +1,17 @@ +import { ParagraphBlockObjectRequest } from "./ParagraphBlockObjectRequest"; + +export function createParagraphBlock( + content: string +): ParagraphBlockObjectRequest { + return { + paragraph: { + rich_text: [ + { + text: { + content: content, + }, + }, + ], + }, + }; +} diff --git a/src/notion/publish/v2/createStatGroupListItemBlock.ts b/src/notion/publish/v2/createStatGroupListItemBlock.ts index 2d8aaff..fe29025 100644 --- a/src/notion/publish/v2/createStatGroupListItemBlock.ts +++ b/src/notion/publish/v2/createStatGroupListItemBlock.ts @@ -1,11 +1,11 @@ -import { BlockObjectRequest } from "@notionhq/client/build/src/api-endpoints"; import { isStatGroupDesc, - StatDesc, StatGroupDesc, StatsType, } from "../../../statistiques/v2/desc/StatsDesc"; -import { formatValue } from "../../../format/formatValue"; +import { createStatListItemBlock } from "./createStatListItemBlock"; +import { BulletedListItemBlockObjectRequest } from "../blocks/BulletedListItemBlockObjectRequest"; +import { BulletedListItemChildren } from "../blocks/BulletedListItemChildren"; export function createStatGroupListItemBlock( descriptor: D, @@ -28,14 +28,6 @@ export function createStatGroupListItemBlock( }; } -export type BulletedListItemBlockObjectRequest = Extract< - BlockObjectRequest, - { bulleted_list_item: object } ->; - -export type BulletedListItemChildren = - BulletedListItemBlockObjectRequest["bulleted_list_item"]["children"]; - export function createStatGroupChildrenListItemBlock( descriptor: D, stats: StatsType @@ -52,21 +44,3 @@ export function createStatGroupChildrenListItemBlock( : createStatListItemBlock(childStatDesc, childStatValue as number); }); } - -function createStatListItemBlock( - descriptor: StatDesc, - statValue: number -): BulletedListItemBlockObjectRequest { - return { - bulleted_list_item: { - rich_text: [ - { - text: { - content: - descriptor.label + ": " + formatValue(statValue, descriptor), - }, - }, - ], - }, - }; -} diff --git a/src/notion/publish/v2/createStatListItemBlock.ts b/src/notion/publish/v2/createStatListItemBlock.ts new file mode 100644 index 0000000..a9ee702 --- /dev/null +++ b/src/notion/publish/v2/createStatListItemBlock.ts @@ -0,0 +1,21 @@ +import { formatValue } from "../../../format/formatValue"; +import { StatDesc } from "../../../statistiques/v2/desc/StatsDesc"; +import { BulletedListItemBlockObjectRequest } from "../blocks/BulletedListItemBlockObjectRequest"; + +export function createStatListItemBlock( + descriptor: StatDesc, + statValue: number +): BulletedListItemBlockObjectRequest { + return { + bulleted_list_item: { + rich_text: [ + { + text: { + content: + descriptor.label + ": " + formatValue(statValue, descriptor), + }, + }, + ], + }, + }; +} diff --git a/src/notion/publish/v2/publishStatsGenerales.ts b/src/notion/publish/v2/publishStatsGenerales.ts new file mode 100644 index 0000000..3a4fbd8 --- /dev/null +++ b/src/notion/publish/v2/publishStatsGenerales.ts @@ -0,0 +1,33 @@ +import { Client } from "@notionhq/client"; +import { createMermaidCodeBlock } from "../blocks/createMermaidCodeBlock"; +import { createParagraphBlock } from "../blocks/createParagraphBlock"; +import { createStatGroupChildrenListItemBlock } from "./createStatGroupListItemBlock"; +import { updatePageContent } from "./updatePageContent"; +import { + StatsGenerales, + statsGeneralesDesc, +} from "../../../statistiques/v2/generales/StatsGenerales"; + +export async function publishStatsGenerales( + notionClient: Client, + header: string, + statsGenerales: StatsGenerales, + mermaidDiagramStatsGeneralesMensuelles: string +) { + const headerBlock = createParagraphBlock(header); + const statsBlocks = createStatGroupChildrenListItemBlock( + statsGeneralesDesc, + statsGenerales + ); + + const diagramBlock = createMermaidCodeBlock( + mermaidDiagramStatsGeneralesMensuelles + ); + + const blocks = [headerBlock, ...statsBlocks, diagramBlock]; + await updatePageContent( + notionClient, + "313751fb-daed-4b33-992f-c86d7ac2de37", + blocks + ); +} diff --git a/src/notion/publish/v2/publishStatsToPage.ts b/src/notion/publish/v2/publishStatsToPage.ts index 5f25d89..2a67495 100644 --- a/src/notion/publish/v2/publishStatsToPage.ts +++ b/src/notion/publish/v2/publishStatsToPage.ts @@ -5,7 +5,7 @@ import { } from "../../../statistiques/v2/desc/StatsDesc"; import { createStatGroupChildrenListItemBlock } from "./createStatGroupListItemBlock"; import { updatePageContent } from "./updatePageContent"; -import { BlockObjectRequest } from "@notionhq/client/build/src/api-endpoints"; +import { createParagraphBlock } from "../blocks/createParagraphBlock"; export async function publishStatsToPage( notionClient: Client, @@ -22,22 +22,3 @@ export async function publishStatsToPage( ...statsBlocks, ]); } - -type ParagraphBlockObjectRequest = Extract< - BlockObjectRequest, - { paragraph: unknown } ->; - -function createParagraphBlock(content: string): ParagraphBlockObjectRequest { - return { - paragraph: { - rich_text: [ - { - text: { - content: content, - }, - }, - ], - }, - }; -} diff --git a/src/statistiques/v1/generateELMonths.ts b/src/period/generateELMonths.ts similarity index 85% rename from src/statistiques/v1/generateELMonths.ts rename to src/period/generateELMonths.ts index 6ac06f7..f8d73e0 100644 --- a/src/statistiques/v1/generateELMonths.ts +++ b/src/period/generateELMonths.ts @@ -1,6 +1,6 @@ import { getMonth, getYear, setMonth, setYear } from "date-fns"; -import { IdentifiedPeriod } from "../../period/IdentifiedPeriod"; -import { generateConsecutiveIdentifiedPeriods } from "../../period/generateConsecutiveIdentifiedPeriods"; +import { IdentifiedPeriod } from "./IdentifiedPeriod"; +import { generateConsecutiveIdentifiedPeriods } from "./generateConsecutiveIdentifiedPeriods"; export function generateELMonths(): IdentifiedPeriod[] { const months = generateConsecutiveIdentifiedPeriods({ diff --git a/src/statistiques/v1/generateELYears.ts b/src/period/generateELYears.ts similarity index 76% rename from src/statistiques/v1/generateELYears.ts rename to src/period/generateELYears.ts index a86dc55..585d235 100644 --- a/src/statistiques/v1/generateELYears.ts +++ b/src/period/generateELYears.ts @@ -1,6 +1,6 @@ import { addYears } from "date-fns/fp"; -import { IdentifiedPeriod } from "../../period/IdentifiedPeriod"; -import { generateConsecutiveIdentifiedPeriods } from "../../period/generateConsecutiveIdentifiedPeriods"; +import { IdentifiedPeriod } from "./IdentifiedPeriod"; +import { generateConsecutiveIdentifiedPeriods } from "./generateConsecutiveIdentifiedPeriods"; export function generateELYears(): IdentifiedPeriod[] { return generateConsecutiveIdentifiedPeriods({ diff --git a/src/statistiques/v1/computeELStats.ts b/src/statistiques/v1/computeELStats.ts index 78325e4..e315663 100644 --- a/src/statistiques/v1/computeELStats.ts +++ b/src/statistiques/v1/computeELStats.ts @@ -2,8 +2,8 @@ import { Famille } from "../../data/Famille"; import { computeELPeriodStats } from "./computeELPeriodStats"; import { computeELStatsAtDate } from "./computeELStatsAtDate"; import { ELStats } from "./ELStats"; -import { generateELMonths } from "./generateELMonths"; -import { generateELYears } from "./generateELYears"; +import { generateELMonths } from "../../period/generateELMonths"; +import { generateELYears } from "../../period/generateELYears"; export function computeELStats( families: Famille[], diff --git a/src/statistiques/v2/generales/computeStatsGeneralesMensuelles.ts b/src/statistiques/v2/generales/computeStatsGeneralesMensuelles.ts new file mode 100644 index 0000000..d1504c3 --- /dev/null +++ b/src/statistiques/v2/generales/computeStatsGeneralesMensuelles.ts @@ -0,0 +1,88 @@ +import { Famille, isExResistant, isResistant } from "../../../data/Famille"; +import { generateELMonths } from "../../../period/generateELMonths"; +import { isPeriodContaining } from "../../../period/isPeriodContaining"; +import { Period } from "../../../period/Period"; + +export type StatsGeneralesMensuelles = { + [month: string]: StatsGeneralesMensuelle; +}; + +export type StatsGeneralesMensuelle = { + nbEntrees: number; + nbSorties: number; + nbEntreesNet: number; + nbFamillesResistantes: number; + nbFamillesExResistantes: number; +}; + +export function computeStatsGeneralesMensuelles( + familles: Famille[] +): StatsGeneralesMensuelles { + const months = generateELMonths(); + return Object.fromEntries( + months.map((period) => { + const nbEntrees = nbEntreeSurPeriode(familles, period); + const nbSorties = nbSortieSurPeriode(familles, period); + const statsMensuelle: StatsGeneralesMensuelle = { + nbEntrees: nbEntrees, + nbSorties: nbSorties, + nbEntreesNet: nbEntrees - nbSorties, + nbFamillesResistantes: nbFamillesResistanteAFinPeriode( + familles, + period + ), + + nbFamillesExResistantes: nbFamillesExResistanteAFinPeriode( + familles, + period + ), + }; + return [period.id, statsMensuelle]; + }) + ); +} + +function nbEntreeSurPeriode(familles: Famille[], period: Period): number { + return familles.filter( + (f) => + (isResistant(f) || isExResistant(f)) && + f.Integration && + isPeriodContaining(period, f.Integration) + ).length; +} + +function nbSortieSurPeriode(familles: Famille[], period: Period): number { + return familles.filter( + (f) => + (isResistant(f) || isExResistant(f)) && + f.Sortie && + isPeriodContaining(period, f.Sortie) + ).length; +} + +function nbFamillesResistanteAFinPeriode( + familles: Famille[], + period: Period +): number { + return familles.filter( + (f) => + (isResistant(f) || isExResistant(f)) && + f.Integration && + f.Integration < period.end && + (!f.Sortie || f.Sortie > period.end) + ).length; +} + +function nbFamillesExResistanteAFinPeriode( + familles: Famille[], + period: Period +): number { + return familles.filter( + (f) => + (isResistant(f) || isExResistant(f)) && + f.Integration && + f.Integration < period.end && + f.Sortie && + f.Sortie < period.end + ).length; +} diff --git a/src/statistiques/v2/generales/mermaidDiagramStatsGeneralesMensuelles.ts b/src/statistiques/v2/generales/mermaidDiagramStatsGeneralesMensuelles.ts new file mode 100644 index 0000000..6c3b50f --- /dev/null +++ b/src/statistiques/v2/generales/mermaidDiagramStatsGeneralesMensuelles.ts @@ -0,0 +1,31 @@ +import { StatsGeneralesMensuelles } from "./computeStatsGeneralesMensuelles"; + +export function mermaidDiagramStatsGeneralesMensuelles( + statsGeneralesMensuelle: StatsGeneralesMensuelles +): string { + const statsEntries = Object.entries(statsGeneralesMensuelle); + const entriesToDisplay = statsEntries.slice( + statsEntries.length - 15, + statsEntries.length + ); + const periods = entriesToDisplay.map(([p]) => { + const comp = p.split("-"); + // Reformat month as MM/yy + return `"${comp[1]}/${comp[0].substring(2, 4)}"`; + }); + return `--- +config: + themeVariables: + xyChart: + plotColorPalette: "#1E88E5, #EEEEEE, #C8E6C9, #EF9A9A" +--- +xychart-beta + title "Familles: Résist.(bleu), Ex-Résist (gris), Entrées (vert), Sorties (rouge)" + x-axis "Mois" [${periods}] + y-axis "Nb Familles" 0 --> 110 + line [${entriesToDisplay.map(([, s]) => s.nbFamillesResistantes)}] + line [${entriesToDisplay.map(([, s]) => s.nbFamillesExResistantes)}] + line [${entriesToDisplay.map(([, s]) => s.nbEntrees)}] + line [${entriesToDisplay.map(([, s]) => s.nbSorties)}] +`; +}