feat: refactor + ajoute Nb familleMuses en demeure
parent
3de3aa2662
commit
54fcebebf0
|
@ -0,0 +1,101 @@
|
|||
import { differenceInDays } from "date-fns";
|
||||
import { Period } from "../period/Period";
|
||||
import { arePeriodsOverlaping } from "../period/arePeriodsOverlaping";
|
||||
import { isPeriodContaining } from "../period/isPeriodContaining";
|
||||
|
||||
export type Famille = {
|
||||
notionId: string;
|
||||
Titre: string;
|
||||
Statut: StatutFamille;
|
||||
Integration: Date | null;
|
||||
Sortie: Date | null;
|
||||
Evenements: EvenementFamille[];
|
||||
};
|
||||
|
||||
export type EvenementFamille = {
|
||||
notionId: string;
|
||||
notionIdFamille: string;
|
||||
Évènement: string;
|
||||
Date: Date | null;
|
||||
Type: TypeEvenement;
|
||||
"Enfants concernés": string;
|
||||
};
|
||||
|
||||
export type TypeEvenement =
|
||||
| "Mise en demeure de scolarisation"
|
||||
| "Composition pénale refusée"
|
||||
| "Composition pénale acceptée";
|
||||
|
||||
export type StatutFamille =
|
||||
| "Résistant.e"
|
||||
| "Ex résistant·e·s"
|
||||
| "À préciser"
|
||||
| "Se questionne"
|
||||
| "Désobéissence décidée"
|
||||
| "Rédaction Déclaration"
|
||||
| "Déclaration Validée - Attente éléments"
|
||||
| "Abdandon"
|
||||
| "Incompatible";
|
||||
|
||||
export function periodOfResistance(
|
||||
family: Famille,
|
||||
atDate: Date = new Date(Date.now())
|
||||
): Period | null {
|
||||
if (family.Statut !== "Résistant.e" && family.Statut !== "Ex résistant·e·s") {
|
||||
return null;
|
||||
}
|
||||
if (!family.Integration || family.Integration.getTime() > atDate.getTime()) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
start: family.Integration,
|
||||
end:
|
||||
family.Sortie !== null && family.Sortie.getTime() < atDate.getTime()
|
||||
? family.Sortie
|
||||
: atDate,
|
||||
};
|
||||
}
|
||||
|
||||
export function isResistant(
|
||||
family: Famille,
|
||||
date: Date = new Date(Date.now())
|
||||
): boolean {
|
||||
const por = periodOfResistance(family, date);
|
||||
return por !== null && isPeriodContaining(por, date);
|
||||
}
|
||||
|
||||
export function isExResistant(
|
||||
family: Famille,
|
||||
date: Date = new Date(Date.now())
|
||||
): boolean {
|
||||
const por = periodOfResistance(family, date);
|
||||
return por !== null && por.end.getTime() < date.getTime();
|
||||
}
|
||||
|
||||
export function isResistantOverPeriod(
|
||||
family: Famille,
|
||||
period: Period
|
||||
): boolean {
|
||||
const familyPeriodResistant: Period | null = periodOfResistance(family);
|
||||
return (
|
||||
familyPeriodResistant !== null &&
|
||||
arePeriodsOverlaping(familyPeriodResistant, period)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param family
|
||||
* @param atDate
|
||||
* @returns the duration of resistance in days or null if family was not yet in resistances at this date
|
||||
*/
|
||||
export function dureeResistanceInDays(
|
||||
family: Famille,
|
||||
atDate: Date = new Date(Date.now())
|
||||
): number | null {
|
||||
const period = periodOfResistance(family, atDate);
|
||||
if (period == null) {
|
||||
return null;
|
||||
}
|
||||
return differenceInDays(period.end, period.start);
|
||||
}
|
|
@ -1,87 +0,0 @@
|
|||
import { arePeriodsOverlaping } from "../period/arePeriodsOverlaping";
|
||||
import { Period } from "../period/Period";
|
||||
|
||||
export type Family = {
|
||||
notionId: string;
|
||||
Titre: string;
|
||||
Statut: StatutFamille;
|
||||
Integration: Date | null;
|
||||
Sortie: Date | null;
|
||||
Evenements: FamilyEvent[];
|
||||
};
|
||||
|
||||
export type FamilyEvent = {
|
||||
notionId: string;
|
||||
notionIdFamille: string;
|
||||
Évènement: string;
|
||||
Date: Date | null;
|
||||
Type: string;
|
||||
"Enfants concernés": string;
|
||||
};
|
||||
|
||||
export type StatutFamille =
|
||||
| "Résistant.e"
|
||||
| "Ex résistant·e·s"
|
||||
| "À préciser"
|
||||
| "Se questionne"
|
||||
| "Désobéissence décidée"
|
||||
| "Rédaction Déclaration"
|
||||
| "Déclaration Validée - Attente éléments"
|
||||
| "Abdandon"
|
||||
| "Incompatible";
|
||||
|
||||
export function isResistant(family: Family): boolean {
|
||||
return (
|
||||
family.Statut === "Résistant.e" &&
|
||||
family.Integration !== null &&
|
||||
family.Sortie === null
|
||||
);
|
||||
}
|
||||
|
||||
export function isExResistant(family: Family): boolean {
|
||||
return (
|
||||
family.Statut === "Ex résistant·e·s" &&
|
||||
family.Integration !== null &&
|
||||
family.Sortie !== null
|
||||
);
|
||||
}
|
||||
|
||||
export function isResistantAtDate(family: Family, date: Date): boolean {
|
||||
if (isResistant(family) && family.Integration!.getTime() <= date.getTime()) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
isExResistant(family) &&
|
||||
family.Integration!.getTime() <= date.getTime() &&
|
||||
family.Sortie!.getTime() > date.getTime()
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isResistantOverPeriod(family: Family, period: Period): boolean {
|
||||
const familyPeriodResistant: Period | null = periodOfResistance(family);
|
||||
return (
|
||||
familyPeriodResistant !== null &&
|
||||
arePeriodsOverlaping(familyPeriodResistant, period)
|
||||
);
|
||||
}
|
||||
|
||||
export function periodOfResistance(family: Family): Period | null {
|
||||
if (isResistant(family)) {
|
||||
const periodResistant: Period = {
|
||||
start: family.Integration!,
|
||||
end: new Date(Date.now()),
|
||||
};
|
||||
return periodResistant;
|
||||
}
|
||||
if (isExResistant(family)) {
|
||||
const periodResistant: Period = {
|
||||
start: family.Integration!,
|
||||
end: family.Sortie!,
|
||||
};
|
||||
return periodResistant;
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Family } from "./Family";
|
||||
import { Famille } from "./Famille";
|
||||
|
||||
export function checkDataConsistency(families: Family[]): ConsistencyIssue[] {
|
||||
export function checkDataConsistency(families: Famille[]): ConsistencyIssue[] {
|
||||
return families.flatMap((family) => {
|
||||
return checkFamilyDataConsistency(family);
|
||||
});
|
||||
|
@ -10,7 +10,7 @@ export type ConsistencyIssue = {
|
|||
issueType: string;
|
||||
familyId: string;
|
||||
};
|
||||
function checkFamilyDataConsistency(family: Family) {
|
||||
function checkFamilyDataConsistency(family: Famille) {
|
||||
const consistencyIssues: ConsistencyIssue[] = [];
|
||||
|
||||
if (family.Statut === "Résistant.e") {
|
||||
|
|
|
@ -30,8 +30,6 @@ import { computeELStats } from "./statistiques/computeELStats";
|
|||
console.log("Building statistics...");
|
||||
const resistantCountStats = computeELStats(families);
|
||||
|
||||
console.log(resistantCountStats);
|
||||
|
||||
console.log("Publishing statistics...");
|
||||
publishStatisticsToNotion(resistantCountStats, notionClient);
|
||||
})();
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { Client, isFullPage } from "@notionhq/client";
|
||||
import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
|
||||
import { Family, FamilyEvent, StatutFamille } from "../../data/Family";
|
||||
import {
|
||||
EvenementFamille,
|
||||
Famille,
|
||||
StatutFamille,
|
||||
TypeEvenement,
|
||||
} from "../../data/Famille";
|
||||
import { datePropertyToDate } from "../utils/properties/datePropertyToDate";
|
||||
import { relationPropertyToPageId } from "../utils/properties/relationPropertyToPageId";
|
||||
import { selectPropertyToText } from "../utils/properties/selectPropertyToText";
|
||||
|
@ -11,7 +16,7 @@ import { richTextPropertyToPlainText } from "../utils/text/richTextPropertyToPla
|
|||
|
||||
export async function fetchFamiliesWithEventsFromNotion(
|
||||
notionClient: Client
|
||||
): Promise<Family[]> {
|
||||
): Promise<Famille[]> {
|
||||
const familiesDbId: string = "5b69e02b296d4a578f8c8ab7fe8b05da";
|
||||
const familEventsDbId: string = "c4d434b4603c4481a4d445618ecdf999";
|
||||
|
||||
|
@ -30,7 +35,7 @@ export async function fetchFamiliesWithEventsFromNotion(
|
|||
return buildFamilyEvent(pageObjectResponse);
|
||||
});
|
||||
|
||||
const families: Family[] = await Promise.all(
|
||||
const families: Famille[] = await Promise.all(
|
||||
familyPages.map((pageObjectResponse) => {
|
||||
return buildFamily(pageObjectResponse, familyEvents);
|
||||
})
|
||||
|
@ -38,13 +43,13 @@ export async function fetchFamiliesWithEventsFromNotion(
|
|||
return families;
|
||||
}
|
||||
|
||||
function buildFamilyEvent(page: PageObjectResponse): FamilyEvent {
|
||||
function buildFamilyEvent(page: PageObjectResponse): EvenementFamille {
|
||||
const pageProperties = page.properties;
|
||||
|
||||
const familyEvent: FamilyEvent = {
|
||||
const familyEvent: EvenementFamille = {
|
||||
notionId: page.id,
|
||||
Évènement: titlePropertyToText(pageProperties, "Évènement"),
|
||||
Type: selectPropertyToText(pageProperties, "Type")!,
|
||||
Type: selectPropertyToText(pageProperties, "Type")! as TypeEvenement,
|
||||
"Enfants concernés": richTextPropertyToPlainText(
|
||||
pageProperties,
|
||||
"Enfants concernés"
|
||||
|
@ -57,11 +62,11 @@ function buildFamilyEvent(page: PageObjectResponse): FamilyEvent {
|
|||
|
||||
function buildFamily(
|
||||
page: PageObjectResponse,
|
||||
familyEvents: FamilyEvent[]
|
||||
): Family {
|
||||
familyEvents: EvenementFamille[]
|
||||
): Famille {
|
||||
const pageProperties = page.properties;
|
||||
|
||||
const family: Family = {
|
||||
const family: Famille = {
|
||||
notionId: page.id,
|
||||
Titre: titlePropertyToText(pageProperties, ""),
|
||||
Statut: statusPropertyToText(pageProperties, "Statut") as StatutFamille,
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
import { Client, isFullPage } from "@notionhq/client";
|
||||
import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
|
||||
import { ELPeriodStats, ValueWithEvol } from "../../statistiques/ELStats";
|
||||
import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
|
||||
import { queryAllDbResults } from "../utils/queryAllDbResults";
|
||||
import { removeBlocks } from "../utils/removeBlocks";
|
||||
import { CreatePageProperties } from "../utils/types/CreatePageProperties";
|
||||
import {
|
||||
statNameDureeResistanceMediane,
|
||||
statNameDureeResistanceMoyenne,
|
||||
statsNameNbFamillesMisesEnDemeure,
|
||||
statsNameNbFamillesResistantes,
|
||||
} from "./statNames";
|
||||
|
||||
export async function publishPeriodStats(
|
||||
notionClient: Client,
|
||||
periodStatsDbId: string,
|
||||
stats: ELPeriodStats[]
|
||||
) {
|
||||
const periodRows = (
|
||||
await queryAllDbResults(notionClient, {
|
||||
database_id: periodStatsDbId,
|
||||
})
|
||||
).filter(isFullPage);
|
||||
|
||||
const indexedPeriodRows: { [period: string]: PageObjectResponse } =
|
||||
Object.fromEntries(
|
||||
periodRows.map((r) => [titlePropertyToText(r.properties, "Période"), r])
|
||||
);
|
||||
|
||||
const indexedPeriodStats = Object.fromEntries(
|
||||
stats.map((stat) => [stat.periodId, stat])
|
||||
);
|
||||
|
||||
const rowIdsToDelete = Object.entries(indexedPeriodRows)
|
||||
.filter(
|
||||
([periodId]) =>
|
||||
!Object.prototype.hasOwnProperty.call(indexedPeriodStats, periodId)
|
||||
)
|
||||
.map(([, row]) => row.id);
|
||||
|
||||
const periodIdsToUpdate = Object.entries(indexedPeriodRows)
|
||||
.filter(([periodId]) =>
|
||||
Object.prototype.hasOwnProperty.call(indexedPeriodStats, periodId)
|
||||
)
|
||||
.map(([periodId]) => periodId);
|
||||
|
||||
const periodIdsToCreate = Object.entries(indexedPeriodStats)
|
||||
.filter(
|
||||
([periodId]) =>
|
||||
!Object.prototype.hasOwnProperty.call(indexedPeriodRows, periodId)
|
||||
)
|
||||
.map(([periodId]) => periodId);
|
||||
|
||||
// Delete rows to delte
|
||||
await removeBlocks(notionClient, rowIdsToDelete);
|
||||
|
||||
// Create rows to create
|
||||
for (const periodId of periodIdsToCreate) {
|
||||
const stat = indexedPeriodStats[periodId];
|
||||
await notionClient.pages.create({
|
||||
parent: {
|
||||
database_id: periodStatsDbId,
|
||||
},
|
||||
properties: buildRowPropertiesForUpsert(stat),
|
||||
});
|
||||
}
|
||||
|
||||
// Update rows
|
||||
for (const periodId of periodIdsToUpdate) {
|
||||
const stat = indexedPeriodStats[periodId];
|
||||
const row = indexedPeriodRows[periodId];
|
||||
await notionClient.pages.update({
|
||||
page_id: row.id,
|
||||
properties: buildRowPropertiesForUpsert(stat),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function buildRowPropertiesForUpsert(
|
||||
stat: ELPeriodStats
|
||||
): CreatePageProperties {
|
||||
return {
|
||||
Période: {
|
||||
title: [
|
||||
{
|
||||
text: {
|
||||
content: stat.periodId,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
[statsNameNbFamillesResistantes]: valueWithEvolProp(
|
||||
stat.nbFamilleResistantes
|
||||
),
|
||||
[statNameDureeResistanceMediane]: valueWithEvolProp(
|
||||
stat.dureeResistanceMediane
|
||||
),
|
||||
[statNameDureeResistanceMoyenne]: valueWithEvolProp(
|
||||
stat.dureeResistanceMoyenne
|
||||
),
|
||||
[statsNameNbFamillesMisesEnDemeure]: valueWithEvolProp(
|
||||
stat.nbFamillesMisesEnDemeure
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function valueWithEvolProp(n: ValueWithEvol) {
|
||||
const formatted = formatValueWithEvol(n);
|
||||
return {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: formatted,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
function formatValueWithEvol(n: ValueWithEvol): string {
|
||||
const value = n.value.toLocaleString("fr-FR", {
|
||||
useGrouping: false,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
if (isNaN(n.evol)) {
|
||||
return value;
|
||||
} else {
|
||||
const evol = n.evol.toLocaleString("fr-FR", {
|
||||
useGrouping: false,
|
||||
maximumFractionDigits: 2,
|
||||
signDisplay: "always",
|
||||
});
|
||||
const evolPercent = Math.round(n.evolPercent).toLocaleString("fr-FR", {
|
||||
useGrouping: false,
|
||||
signDisplay: "always",
|
||||
});
|
||||
|
||||
return `${value} (${evol} | ${evolPercent}%)`;
|
||||
}
|
||||
}
|
|
@ -1,14 +1,7 @@
|
|||
import { Client, isFullPage } from "@notionhq/client";
|
||||
import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
|
||||
import {
|
||||
ELPeriodStats,
|
||||
ELStats,
|
||||
ValueWithEvol,
|
||||
} from "../../statistiques/ELStats";
|
||||
import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
|
||||
import { queryAllDbResults } from "../utils/queryAllDbResults";
|
||||
import { removeBlocks } from "../utils/removeBlocks";
|
||||
import { publishCurrentStats } from "./publishCurrentStats";
|
||||
import { Client } from "@notionhq/client";
|
||||
import { ELStats } from "../../statistiques/ELStats";
|
||||
import { publishPeriodStats } from "./publishPeriodStats";
|
||||
import { publishStatsActuelles } from "./publishStatsActuelles";
|
||||
|
||||
export const statsPageId = "2b91cd90e3694e96bb196d69aeca59b1";
|
||||
export const currentStatsHeading = "Statistiques actuelles";
|
||||
|
@ -19,123 +12,9 @@ export async function publishStatisticsToNotion(
|
|||
stats: ELStats,
|
||||
notionClient: Client
|
||||
) {
|
||||
await publishCurrentStats(notionClient, stats);
|
||||
await publishStatsActuelles(notionClient, stats.actuelles);
|
||||
|
||||
await publishPeriodStats(notionClient, yearStatsDb, stats.annees);
|
||||
|
||||
await publishPeriodStats(notionClient, monthStatsDb, stats.mois);
|
||||
}
|
||||
|
||||
async function publishPeriodStats(
|
||||
notionClient: Client,
|
||||
periodStatsDbId: string,
|
||||
stats: ELPeriodStats[]
|
||||
) {
|
||||
const periodRows = (
|
||||
await queryAllDbResults(notionClient, {
|
||||
database_id: periodStatsDbId,
|
||||
})
|
||||
).filter(isFullPage);
|
||||
|
||||
const indexedPeriodRows: { [period: string]: PageObjectResponse } =
|
||||
Object.fromEntries(
|
||||
periodRows.map((r) => [titlePropertyToText(r.properties, "Période"), r])
|
||||
);
|
||||
|
||||
const indexedPeriodStats = Object.fromEntries(
|
||||
stats.map((stat) => [stat.periodId, stat])
|
||||
);
|
||||
|
||||
const rowIdsToDelete = Object.entries(indexedPeriodRows)
|
||||
.filter(
|
||||
([periodId]) =>
|
||||
!Object.prototype.hasOwnProperty.call(indexedPeriodStats, periodId)
|
||||
)
|
||||
.map(([, row]) => row.id);
|
||||
|
||||
const periodIdsToUpdate = Object.entries(indexedPeriodRows)
|
||||
.filter(([periodId]) =>
|
||||
Object.prototype.hasOwnProperty.call(indexedPeriodStats, periodId)
|
||||
)
|
||||
.map(([periodId]) => periodId);
|
||||
|
||||
const periodIdsToCreate = Object.entries(indexedPeriodStats)
|
||||
.filter(
|
||||
([periodId]) =>
|
||||
!Object.prototype.hasOwnProperty.call(indexedPeriodRows, periodId)
|
||||
)
|
||||
.map(([periodId]) => periodId);
|
||||
|
||||
// Delete rows to delte
|
||||
await removeBlocks(notionClient, rowIdsToDelete);
|
||||
|
||||
// Create rows to create
|
||||
for (const periodId of periodIdsToCreate) {
|
||||
const stat = indexedPeriodStats[periodId];
|
||||
await notionClient.pages.create({
|
||||
parent: {
|
||||
database_id: periodStatsDbId,
|
||||
},
|
||||
properties: buildRowPropertiesForUpsert(stat),
|
||||
});
|
||||
}
|
||||
|
||||
// Update rows
|
||||
for (const periodId of periodIdsToUpdate) {
|
||||
const stat = indexedPeriodStats[periodId];
|
||||
const row = indexedPeriodRows[periodId];
|
||||
await notionClient.pages.update({
|
||||
page_id: row.id,
|
||||
properties: buildRowPropertiesForUpsert(stat),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function buildRowPropertiesForUpsert(stat: ELPeriodStats) {
|
||||
return {
|
||||
Période: {
|
||||
title: [
|
||||
{
|
||||
text: {
|
||||
content: stat.periodId,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"Nb Famille Résistante": valueWithEvolProp(stat.nbFamilleResistantes),
|
||||
};
|
||||
}
|
||||
|
||||
function valueWithEvolProp(n: ValueWithEvol) {
|
||||
const formatted = formatValueWithEvol(n);
|
||||
return {
|
||||
rich_text: [
|
||||
{
|
||||
text: {
|
||||
content: formatted,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
function formatValueWithEvol(n: ValueWithEvol): string {
|
||||
const value = n.value.toLocaleString("fr-FR", {
|
||||
useGrouping: false,
|
||||
maximumFractionDigits: 2,
|
||||
});
|
||||
if (isNaN(n.evol)) {
|
||||
return value;
|
||||
} else {
|
||||
const evol = n.evol.toLocaleString("fr-FR", {
|
||||
useGrouping: false,
|
||||
maximumFractionDigits: 2,
|
||||
signDisplay: "always",
|
||||
});
|
||||
const evolPercent = Math.round(n.evolPercent).toLocaleString("fr-FR", {
|
||||
useGrouping: false,
|
||||
signDisplay: "always",
|
||||
});
|
||||
|
||||
return `${value} (${evol} | ${evolPercent}%)`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
import { Client, isFullBlock } from "@notionhq/client";
|
||||
import { BlockObjectRequest } from "@notionhq/client/build/src/api-endpoints";
|
||||
import { ELStats } from "../../statistiques/ELStats";
|
||||
import { ELStatsActuelles } from "../../statistiques/ELStats";
|
||||
import { listAllChildrenBlocks } from "../utils/listAllChildrenBlocks";
|
||||
import { removeBlocks } from "../utils/removeBlocks";
|
||||
import { richTextToPlainText } from "../utils/text/richTextToPlainText";
|
||||
import { currentStatsHeading, statsPageId } from "./publishStatisticsToNotion";
|
||||
import {
|
||||
statNameDureeResistanceMediane,
|
||||
statNameDureeResistanceMoyenne,
|
||||
statsNameNbFamillesMisesEnDemeure,
|
||||
statsNameNbFamillesResistantes,
|
||||
statsNameNbFamillesResistantesOuEx,
|
||||
statsNamePourcentageFamilleMisesEnDemeure,
|
||||
} from "./statNames";
|
||||
|
||||
export async function publishCurrentStats(
|
||||
export async function publishStatsActuelles(
|
||||
notionClient: Client,
|
||||
stats: ELStats
|
||||
statsActuelles: ELStatsActuelles
|
||||
) {
|
||||
const childrenBlocks = (
|
||||
await listAllChildrenBlocks(notionClient, {
|
||||
|
@ -41,18 +49,29 @@ export async function publishCurrentStats(
|
|||
block_id: statsPageId,
|
||||
after: currentStatsHeadingBlock.id,
|
||||
children: [
|
||||
currentStatBlock("Nb Famille Résistante", stats.nbFamilleResistantes),
|
||||
currentStatBlock(
|
||||
"Nb Famille Résistante ou Ex-Résistante",
|
||||
stats.nbFamilleResistantesOrEx
|
||||
statsNameNbFamillesResistantes,
|
||||
statsActuelles.nbFamilleResistantes
|
||||
),
|
||||
currentStatBlock(
|
||||
"Durée Moyenne Résistance (jours)",
|
||||
stats.dureeMoyenneResistance
|
||||
statsNameNbFamillesResistantesOuEx,
|
||||
statsActuelles.nbFamilleResistantesOrEx
|
||||
),
|
||||
currentStatBlock(
|
||||
"Durée Médiane Résistance (jours)",
|
||||
stats.dureeMedianeResistance
|
||||
statNameDureeResistanceMoyenne,
|
||||
statsActuelles.dureeResistanceMoyenne
|
||||
),
|
||||
currentStatBlock(
|
||||
statNameDureeResistanceMediane,
|
||||
statsActuelles.dureeResistanceMediane
|
||||
),
|
||||
currentStatBlock(
|
||||
statsNameNbFamillesMisesEnDemeure,
|
||||
statsActuelles.nbFamillesMiseEnDemeure
|
||||
),
|
||||
currentStatBlock(
|
||||
statsNamePourcentageFamilleMisesEnDemeure,
|
||||
statsActuelles.pourcentageFamillesMisesEnDemeure
|
||||
),
|
||||
],
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
export const statNameDureeResistanceMoyenne = "Durée Résistance Moyenne";
|
||||
export const statNameDureeResistanceMediane = "Durée Résistance Médiane";
|
||||
export const statsNameNbFamillesResistantes = "Nb Familles Résistantes";
|
||||
export const statsNameNbFamillesResistantesOuEx =
|
||||
"Nb Familles Résistante ou Ex-Résistantes";
|
||||
export const statsNameNbFamillesMisesEnDemeure = "Nb Familles Mises en Demeure";
|
||||
export const statsNamePourcentageFamilleMisesEnDemeure =
|
||||
"Pourcentage de Familles Mises en Demeure";
|
|
@ -0,0 +1,3 @@
|
|||
import { CreatePageParameters } from "@notionhq/client/build/src/api-endpoints";
|
||||
|
||||
export type CreatePageProperties = CreatePageParameters["properties"];
|
|
@ -1,5 +1,4 @@
|
|||
export type Period = {
|
||||
start: Date;
|
||||
/** Exclusive */
|
||||
end: Date;
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@ describe("isPeriodContaining", () => {
|
|||
).toBe(true);
|
||||
});
|
||||
|
||||
test("period does not contain end date", () => {
|
||||
test("period contains end date", () => {
|
||||
expect(
|
||||
isPeriodContaining(
|
||||
{
|
||||
|
@ -34,7 +34,7 @@ describe("isPeriodContaining", () => {
|
|||
},
|
||||
new Date(Date.UTC(2024, 3, 1))
|
||||
)
|
||||
).toBe(false);
|
||||
).toBe(true);
|
||||
});
|
||||
test("period does not contain date before", () => {
|
||||
expect(
|
||||
|
|
|
@ -7,7 +7,7 @@ export function isPeriodContaining(
|
|||
if (dateOrPeriod instanceof Date) {
|
||||
return (
|
||||
period.start.getTime() <= dateOrPeriod.getTime() &&
|
||||
dateOrPeriod.getTime() < period.end.getTime()
|
||||
dateOrPeriod.getTime() <= period.end.getTime()
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
|
|
|
@ -1,19 +1,26 @@
|
|||
export type ELStats = {
|
||||
actuelles: ELStatsActuelles;
|
||||
annees: ELPeriodStats[];
|
||||
|
||||
mois: ELPeriodStats[];
|
||||
};
|
||||
export type ELStatsActuelles = {
|
||||
nbFamilleResistantes: number;
|
||||
/** Includes Ancient resistants */
|
||||
nbFamilleResistantesOrEx: number;
|
||||
|
||||
dureeMoyenneResistance: number;
|
||||
dureeMedianeResistance: number;
|
||||
|
||||
annees: ELPeriodStats[];
|
||||
|
||||
mois: ELPeriodStats[];
|
||||
dureeResistanceMoyenne: number;
|
||||
dureeResistanceMediane: number;
|
||||
nbFamillesMiseEnDemeure: number;
|
||||
pourcentageFamillesMisesEnDemeure: number;
|
||||
};
|
||||
|
||||
export type ELPeriodStats = {
|
||||
periodId: string;
|
||||
nbFamilleResistantes: ValueWithEvol;
|
||||
dureeResistanceMoyenne: ValueWithEvol;
|
||||
dureeResistanceMediane: ValueWithEvol;
|
||||
nbFamillesMisesEnDemeure: ValueWithEvol;
|
||||
};
|
||||
|
||||
export type ValueWithEvol = {
|
||||
|
|
|
@ -1,24 +1,59 @@
|
|||
import { Family, isResistantOverPeriod } from "../data/Family";
|
||||
import {
|
||||
Famille,
|
||||
dureeResistanceInDays,
|
||||
isResistantOverPeriod,
|
||||
} from "../data/Famille";
|
||||
import { IdentifiedPeriod } from "../period/IdentifiedPeriod";
|
||||
import { isPeriodContaining } from "../period/isPeriodContaining";
|
||||
import { ELPeriodStats, ValueWithEvol } from "./ELStats";
|
||||
import { average } from "./math/average";
|
||||
import { median } from "./math/median";
|
||||
|
||||
export function computeELPeriodStats(
|
||||
familles: Family[],
|
||||
familles: Famille[],
|
||||
periods: IdentifiedPeriod[]
|
||||
): ELPeriodStats[] {
|
||||
const periodStats: ELPeriodStats[] = [];
|
||||
let previousELPeriodStats: ELPeriodStats | null = null;
|
||||
for (const period of periods) {
|
||||
const resistantsCount = familles.filter((famille) =>
|
||||
const nbFamilleResistantes = familles.filter((famille) =>
|
||||
isResistantOverPeriod(famille, period)
|
||||
).length;
|
||||
|
||||
const periodEndOrNow =
|
||||
period.end.getTime() > Date.now() ? new Date(Date.now()) : period.end;
|
||||
|
||||
const dureesResistances = familles
|
||||
.map((famille) => dureeResistanceInDays(famille, periodEndOrNow))
|
||||
.filter(notNull);
|
||||
const dureeResistanceMediane = Math.round(median(dureesResistances));
|
||||
const dureeResistanceMoyenne = Math.round(average(dureesResistances));
|
||||
const nbFamillesMiseEnDemeure = familles.filter((famille) =>
|
||||
famille.Evenements.find(
|
||||
(e) =>
|
||||
e.Type === "Mise en demeure de scolarisation" &&
|
||||
e.Date &&
|
||||
isPeriodContaining(period, e.Date)
|
||||
)
|
||||
).length;
|
||||
const stats: ELPeriodStats = {
|
||||
periodId: period.id,
|
||||
nbFamilleResistantes: valueWithEvol(
|
||||
resistantsCount,
|
||||
nbFamilleResistantes,
|
||||
previousELPeriodStats?.nbFamilleResistantes.value
|
||||
),
|
||||
dureeResistanceMediane: valueWithEvol(
|
||||
dureeResistanceMediane,
|
||||
previousELPeriodStats?.dureeResistanceMediane.value
|
||||
),
|
||||
dureeResistanceMoyenne: valueWithEvol(
|
||||
dureeResistanceMoyenne,
|
||||
previousELPeriodStats?.dureeResistanceMoyenne.value
|
||||
),
|
||||
nbFamillesMisesEnDemeure: valueWithEvol(
|
||||
nbFamillesMiseEnDemeure,
|
||||
previousELPeriodStats?.nbFamillesMisesEnDemeure.value
|
||||
),
|
||||
};
|
||||
periodStats.push(stats);
|
||||
previousELPeriodStats = stats;
|
||||
|
@ -46,3 +81,9 @@ function evol(current: number, previous: number | undefined): number {
|
|||
if (previous === undefined) return NaN;
|
||||
return current - previous;
|
||||
}
|
||||
|
||||
// Typescript is not able to infer this inline see
|
||||
// https://stackoverflow.com/questions/43118692/typescript-filter-out-nulls-from-an-array
|
||||
function notNull<TValue>(value: TValue | null): value is TValue {
|
||||
return value !== null;
|
||||
}
|
||||
|
|
|
@ -1,28 +1,12 @@
|
|||
import {
|
||||
Family,
|
||||
isExResistant,
|
||||
isResistant,
|
||||
periodOfResistance,
|
||||
} from "../data/Family";
|
||||
import { daysInPeriod } from "../period/daysInPeriod";
|
||||
import { Famille } from "../data/Famille";
|
||||
import { ELStats } from "./ELStats";
|
||||
import { computeELPeriodStats } from "./computeELPeriodStats";
|
||||
import { computeStatsActuelles } from "./computeStatsActuelles";
|
||||
import { generateELMonths } from "./generateELMonths";
|
||||
import { generateELYears } from "./generateELYears";
|
||||
import { average } from "./math/average";
|
||||
import { median } from "./math/median";
|
||||
|
||||
export function computeELStats(families: Family[]): ELStats {
|
||||
const resistantsCount = families.filter(isResistant).length;
|
||||
const resistantsOrEx = families.filter(
|
||||
(f) => isResistant(f) || isExResistant(f)
|
||||
);
|
||||
const durations = resistantsOrEx.map((f) =>
|
||||
daysInPeriod(periodOfResistance(f)!)
|
||||
);
|
||||
const dureeMoyenne = average(durations);
|
||||
const dureeMediane = median(durations);
|
||||
|
||||
export function computeELStats(families: Famille[]): ELStats {
|
||||
const actuelles = computeStatsActuelles(families);
|
||||
const elYears = generateELYears();
|
||||
const yearsStats = computeELPeriodStats(families, elYears);
|
||||
|
||||
|
@ -30,10 +14,7 @@ export function computeELStats(families: Family[]): ELStats {
|
|||
const monthsStats = computeELPeriodStats(families, months);
|
||||
|
||||
return {
|
||||
nbFamilleResistantes: resistantsCount,
|
||||
nbFamilleResistantesOrEx: resistantsOrEx.length,
|
||||
dureeMoyenneResistance: dureeMoyenne,
|
||||
dureeMedianeResistance: dureeMediane,
|
||||
actuelles: actuelles,
|
||||
annees: yearsStats,
|
||||
mois: monthsStats,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import {
|
||||
Famille,
|
||||
isExResistant,
|
||||
isResistant,
|
||||
periodOfResistance,
|
||||
} from "../data/Famille";
|
||||
import { daysInPeriod } from "../period/daysInPeriod";
|
||||
import { ELStatsActuelles } from "./ELStats";
|
||||
import { average } from "./math/average";
|
||||
import { median } from "./math/median";
|
||||
|
||||
export function computeStatsActuelles(familles: Famille[]): ELStatsActuelles {
|
||||
const resistantsCount = familles.filter((f) => isResistant(f)).length;
|
||||
const resistantsOrEx = familles.filter(
|
||||
(f) => isResistant(f) || isExResistant(f)
|
||||
);
|
||||
const durations = resistantsOrEx.map((f) =>
|
||||
daysInPeriod(periodOfResistance(f)!)
|
||||
);
|
||||
const dureeMoyenne = average(durations);
|
||||
const dureeMediane = median(durations);
|
||||
|
||||
const familleAvecMiseEnDemeure = resistantsOrEx.filter((f) =>
|
||||
f.Evenements.find((e) => e.Type === "Mise en demeure de scolarisation")
|
||||
);
|
||||
|
||||
const actuelles: ELStatsActuelles = {
|
||||
nbFamilleResistantes: resistantsCount,
|
||||
nbFamilleResistantesOrEx: resistantsOrEx.length,
|
||||
dureeResistanceMoyenne: dureeMoyenne,
|
||||
dureeResistanceMediane: dureeMediane,
|
||||
nbFamillesMiseEnDemeure: familleAvecMiseEnDemeure.length,
|
||||
pourcentageFamillesMisesEnDemeure:
|
||||
(100 * familleAvecMiseEnDemeure.length) / resistantsOrEx.length,
|
||||
};
|
||||
return actuelles;
|
||||
}
|
Loading…
Reference in New Issue