feat: ajoute dureeMoyenneResistance dureeMedianeResistance

wip-related-pages
sebastien.arod@gmail.com 2024-06-02 21:00:42 +02:00
parent 74b7fa8f0e
commit 962bdcde80
15 changed files with 218 additions and 110 deletions

View File

@ -1,7 +1,25 @@
import { arePeriodsOverlaping } from "../period/arePeriodsOverlaping";
import { Period } from "../period/Period";
export type FamilyStatus =
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"
@ -11,43 +29,31 @@ export type FamilyStatus =
| "Déclaration Validée - Attente éléments"
| "Abdandon"
| "Incompatible";
export type Family = {
notionId: string;
title: string;
status: FamilyStatus;
startResistsant: Date | null;
endResistant: Date | null;
evenements: FamilyEvent[];
};
export function isResistant(family: Family): boolean {
return (
family.status === "Résistant.e" &&
family.startResistsant !== null &&
family.endResistant === null
family.Statut === "Résistant.e" &&
family.Integration !== null &&
family.Sortie === null
);
}
export function isExResistant(family: Family): boolean {
return (
family.status === "Ex résistant·e·s" &&
family.startResistsant !== null &&
family.endResistant !== null
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.startResistsant!.getTime() <= date.getTime()
) {
if (isResistant(family) && family.Integration!.getTime() <= date.getTime()) {
return true;
}
if (
isExResistant(family) &&
family.startResistsant!.getTime() <= date.getTime() &&
family.endResistant!.getTime() > date.getTime()
family.Integration!.getTime() <= date.getTime() &&
family.Sortie!.getTime() > date.getTime()
) {
return true;
}
@ -62,25 +68,20 @@ export function isResistantOverPeriod(family: Family, period: Period): boolean {
);
}
function periodOfResistance(family: Family): Period | null {
export function periodOfResistance(family: Family): Period | null {
if (isResistant(family)) {
const periodResistant: Period = {
start: family.startResistsant!,
start: family.Integration!,
end: new Date(Date.now()),
};
return periodResistant;
}
if (isExResistant(family)) {
const periodResistant: Period = {
start: family.startResistsant!,
end: family.endResistant!,
start: family.Integration!,
end: family.Sortie!,
};
return periodResistant;
}
return null;
}
export type FamilyEvent = {
date: Date;
type: string;
};

View File

@ -13,49 +13,49 @@ export type ConsistencyIssue = {
function checkFamilyDataConsistency(family: Family) {
const consistencyIssues: ConsistencyIssue[] = [];
if (family.status === "Résistant.e") {
if (family.startResistsant === null) {
if (family.Statut === "Résistant.e") {
if (family.Integration === null) {
consistencyIssues.push({
familyId: family.title,
familyId: family.Titre,
issueType: "Résistant.e without startResistant",
});
}
if (family.endResistant !== null) {
if (family.Sortie !== null) {
consistencyIssues.push({
familyId: family.title,
familyId: family.Titre,
issueType: "Résistant.e with endResistant!!",
});
}
} else if (family.status === "Ex résistant·e·s") {
if (family.startResistsant === null) {
} else if (family.Statut === "Ex résistant·e·s") {
if (family.Integration === null) {
consistencyIssues.push({
familyId: family.title,
familyId: family.Titre,
issueType: "Ex résistant.e.s without startResistant",
});
}
if (family.endResistant === null) {
if (family.Sortie === null) {
consistencyIssues.push({
familyId: family.title,
familyId: family.Titre,
issueType: "Ex résistant.e.s without endResistant",
});
}
if (family.startResistsant!.getTime() > family.endResistant!.getTime()) {
if (family.Integration!.getTime() > family.Sortie!.getTime()) {
consistencyIssues.push({
familyId: family.title,
familyId: family.Titre,
issueType: "startResistsant > endResistant ",
});
}
} else {
if (family.startResistsant !== null) {
if (family.Integration !== null) {
consistencyIssues.push({
familyId: family.title,
issueType: family.status + " with startResistant",
familyId: family.Titre,
issueType: family.Statut + " with startResistant",
});
}
if (family.endResistant !== null) {
if (family.Sortie !== null) {
consistencyIssues.push({
familyId: family.title,
issueType: family.status + " with endResistant",
familyId: family.Titre,
issueType: family.Statut + " with endResistant",
});
}
}

View File

@ -1,50 +1,73 @@
import { Client } from "@notionhq/client";
import {
PageObjectResponse,
QueryDatabaseParameters,
} from "@notionhq/client/build/src/api-endpoints";
import { Family, FamilyStatus } from "../../data/Family";
import { Client, isFullPage } from "@notionhq/client";
import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
import { Family, FamilyEvent, StatutFamille } from "../../data/Family";
import { datePropertyToDate } from "../utils/properties/datePropertyToDate";
import { relationPropertyToPageId } from "../utils/properties/relationPropertyToPAgeId";
import { selectPropertyToText } from "../utils/properties/selectPropertyToText";
import { statusPropertyToText } from "../utils/properties/statusPropertyToText";
import { titlePropertyToText } from "../utils/properties/titlePropertyToText copy";
import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
import { queryAllDbResults } from "../utils/queryAllDbResults";
import { assertFullPage } from "../utils/types/assertFullPage";
import { richTextPropertyToPlainText } from "../utils/text/richTextPropertyToPlainText";
export async function fetchFamiliesWithEventsFromNotion(
notionClient: Client
): Promise<Family[]> {
const familiesDbId: string = "5b69e02b296d4a578f8c8ab7fe8b05da";
const dbQuery: QueryDatabaseParameters = {
database_id: familiesDbId,
/*filter: {
property: "Statut",
status: {
equals: "Résistant.e",
},
},*/
};
const familEventsDbId: string = "c4d434b4603c4481a4d445618ecdf999";
const eventPages = (
await queryAllDbResults(notionClient, {
database_id: familEventsDbId,
})
).filter(isFullPage);
const familyPages = (
await queryAllDbResults(notionClient, {
database_id: familiesDbId,
})
).filter(isFullPage);
const familyEvents = eventPages.map((pageObjectResponse) => {
return buildFamilyEvent(pageObjectResponse);
});
const results = await queryAllDbResults(notionClient, dbQuery);
const families: Family[] = await Promise.all(
results.map((pageObjectResponse) => {
assertFullPage(pageObjectResponse);
return buildFamily(pageObjectResponse);
familyPages.map((pageObjectResponse) => {
return buildFamily(pageObjectResponse, familyEvents);
})
);
return families;
}
function buildFamily(page: PageObjectResponse): Family {
function buildFamilyEvent(page: PageObjectResponse): FamilyEvent {
const pageProperties = page.properties;
const familyEvent: FamilyEvent = {
notionId: page.id,
Évènement: titlePropertyToText(pageProperties, "Évènement"),
Type: selectPropertyToText(pageProperties, "Type")!,
"Enfants concernés": richTextPropertyToPlainText(
pageProperties,
"Enfants concernés"
),
Date: datePropertyToDate(pageProperties, "Date"),
notionIdFamille: relationPropertyToPageId(pageProperties, "Famille")!,
};
return familyEvent;
}
function buildFamily(
page: PageObjectResponse,
familyEvents: FamilyEvent[]
): Family {
const pageProperties = page.properties;
// TODO Fetch Family Events
const family: Family = {
notionId: page.id,
title: titlePropertyToText(pageProperties, ""),
status: statusPropertyToText(pageProperties, "Statut") as FamilyStatus,
startResistsant: datePropertyToDate(pageProperties, "Intégration"),
endResistant: datePropertyToDate(pageProperties, "Sortie"),
evenements: [],
Titre: titlePropertyToText(pageProperties, ""),
Statut: statusPropertyToText(pageProperties, "Statut") as StatutFamille,
Integration: datePropertyToDate(pageProperties, "Intégration"),
Sortie: datePropertyToDate(pageProperties, "Sortie"),
Evenements: familyEvents.filter((fe) => fe.notionIdFamille === page.id),
};
return family;
}

View File

@ -41,10 +41,18 @@ export async function publishCurrentStats(
block_id: statsPageId,
after: currentStatsHeadingBlock.id,
children: [
currentStatBlock("Nb Famille Résistante", stats.resistantsCount),
currentStatBlock("Nb Famille Résistante", stats.nbFamilleResistantes),
currentStatBlock(
"Nb Famille Résistante ou Ex-Résistante",
stats.resistantsOrExCount
stats.nbFamilleResistantesOrEx
),
currentStatBlock(
"Durée Moyenne Résistance (jours)",
stats.dureeMoyenneResistance
),
currentStatBlock(
"Durée Médiane Résistance (jours)",
stats.dureeMedianeResistance
),
],
});

View File

@ -15,9 +15,9 @@ export async function publishStatisticsToNotion(
) {
await publishCurrentStats(notionClient, stats);
await publishPeriodStats(notionClient, yearStatsDb, stats.years);
await publishPeriodStats(notionClient, yearStatsDb, stats.annees);
await publishPeriodStats(notionClient, monthStatsDb, stats.months);
await publishPeriodStats(notionClient, monthStatsDb, stats.mois);
}
async function publishPeriodStats(
@ -50,10 +50,12 @@ async function publishPeriodStats(
},
],
},
"Nb Famille Résistante": numberProp(stat.resistantsCount),
"Nb Famille Résistante - Evol": numberProp(stat.resistantsCountEvol),
"Nb Famille Résistante": numberProp(stat.nbFamilleResistantes),
"Nb Famille Résistante - Evol": numberProp(
stat.nbFamilleResistantesEvol
),
"Nb Famille Résistante - Evol %": numberProp(
stat.resistantsCountEvolPercent
stat.nbFamilleResistantesEvolPercent
),
},
});

View File

@ -0,0 +1,18 @@
import { PageProperties } from "../types/PageProperties";
import { extractPagePropertyValue } from "./extractPagePropertyValue";
export function relationPropertyToPageId(
pageProperties: PageProperties,
propName: string
): string | null {
const propValue = extractPagePropertyValue(pageProperties, propName);
if (propValue.type !== "relation") {
throw new Error(
`Property ${propName} was expected to have type "relation" but got "${propValue.type}".`
);
}
if (propValue.relation.length === 0) {
return null;
}
return propValue.relation[0].id;
}

View File

@ -0,0 +1,18 @@
import { PageProperties } from "../types/PageProperties";
import { extractPagePropertyValue } from "./extractPagePropertyValue";
export function selectPropertyToText(
pageProperties: PageProperties,
propName: string
): string | null {
const propValue = extractPagePropertyValue(pageProperties, propName);
if (propValue.type !== "select") {
throw new Error(
`Property ${propName} was expected to have type "select" but got "${propValue.type}".`
);
}
if (propValue.select === null) {
return null;
}
return propValue.select!.name;
}

View File

@ -4,12 +4,15 @@ import { extractPagePropertyValue } from "./extractPagePropertyValue";
export function statusPropertyToText(
pageProperties: PageProperties,
propName: string
): string {
): string | null {
const propValue = extractPagePropertyValue(pageProperties, propName);
if (propValue.type !== "status") {
throw new Error(
`Property ${propName} was expected to have type "title" but got "${propValue.type}".`
`Property ${propName} was expected to have type "status" but got "${propValue.type}".`
);
}
if (propValue.status === null) {
return null;
}
return propValue.status!.name;
}

View File

@ -0,0 +1,6 @@
import { differenceInDays } from "date-fns";
import { Period } from "./Period";
export function daysInPeriod(period: Period): number {
return differenceInDays(period.end, period.start);
}

View File

@ -1,21 +1,19 @@
export type ELStats = {
/** Current Resistants Count */
resistantsCount: number;
nbFamilleResistantes: number;
/** Includes Ancient resistants */
resistantsOrExCount: number;
nbFamilleResistantesOrEx: number;
/**
* Resistant count per Year
* Family Partially resistant over a year are counted in.
*/
years: ELPeriodStats[];
dureeMoyenneResistance: number;
dureeMedianeResistance: number;
months: ELPeriodStats[];
annees: ELPeriodStats[];
mois: ELPeriodStats[];
};
export type ELPeriodStats = {
periodId: string;
resistantsCount: number;
resistantsCountEvol: number;
resistantsCountEvolPercent: number;
nbFamilleResistantes: number;
nbFamilleResistantesEvol: number;
nbFamilleResistantesEvolPercent: number;
};

View File

@ -15,14 +15,14 @@ export function computeELPeriodStats(
const stats: ELPeriodStats = {
periodId: period.id,
resistantsCount,
resistantsCountEvol: evol(
nbFamilleResistantes: resistantsCount,
nbFamilleResistantesEvol: evol(
resistantsCount,
previousELPeriodStats?.resistantsCount
previousELPeriodStats?.nbFamilleResistantes
),
resistantsCountEvolPercent: evolPercent(
nbFamilleResistantesEvolPercent: evolPercent(
resistantsCount,
previousELPeriodStats?.resistantsCount
previousELPeriodStats?.nbFamilleResistantes
),
};
periodStats.push(stats);

View File

@ -1,14 +1,27 @@
import { Family, isExResistant, isResistant } from "../data/Family";
import {
Family,
isExResistant,
isResistant,
periodOfResistance,
} from "../data/Family";
import { daysInPeriod } from "../period/daysInPeriod";
import { ELStats } from "./ELStats";
import { computeELPeriodStats } from "./computeELPeriodStats";
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 resistantsOrExCount = families.filter(
const resistantsOrEx = families.filter(
(f) => isResistant(f) || isExResistant(f)
).length;
);
const durations = resistantsOrEx.map((f) =>
daysInPeriod(periodOfResistance(f)!)
);
const dureeMoyenne = average(durations);
const dureeMediane = median(durations);
const elYears = generateELYears();
const yearsStats = computeELPeriodStats(families, elYears);
@ -17,9 +30,11 @@ export function computeELStats(families: Family[]): ELStats {
const monthsStats = computeELPeriodStats(families, months);
return {
resistantsCount,
resistantsOrExCount,
years: yearsStats,
months: monthsStats,
nbFamilleResistantes: resistantsCount,
nbFamilleResistantesOrEx: resistantsOrEx.length,
dureeMoyenneResistance: dureeMoyenne,
dureeMedianeResistance: dureeMediane,
annees: yearsStats,
mois: monthsStats,
};
}

View File

@ -0,0 +1,5 @@
export function average(values: number[]): number {
if (values.length === 0) return NaN;
return values.reduce((a, b) => a + b) / values.length;
}

View File

@ -0,0 +1,11 @@
export function median(values: number[]): number {
if (values.length === 0) return NaN;
const sorted = [...values].sort((a, b) => a - b);
const half = Math.floor(sorted.length / 2);
return sorted.length % 2
? sorted[half]
: (sorted[half - 1] + sorted[half]) / 2;
}