feat: ajoute dureeMoyenneResistance dureeMedianeResistance
parent
74b7fa8f0e
commit
962bdcde80
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
),
|
||||
],
|
||||
});
|
||||
|
|
|
@ -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
|
||||
),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue