feat: stats social basé sur propriété

This commit is contained in:
Sébastien Arod 2024-09-08 11:55:11 +02:00
parent 80172ebb11
commit 8dda887349
13 changed files with 196 additions and 84 deletions

View file

@ -5,6 +5,8 @@ import { isPeriodContaining } from "../period/isPeriodContaining";
import { ContexteEntreeDC } from "./ContexteEntreeDC"; import { ContexteEntreeDC } from "./ContexteEntreeDC";
import { EvenementFamille } from "./EvenementFamille"; import { EvenementFamille } from "./EvenementFamille";
import { StatutFamille } from "./StatutFamille"; import { StatutFamille } from "./StatutFamille";
import { StatutSocial } from "./StatutSocial";
import { StatutPenal } from "./StatutPenal";
export type Famille = Readonly<{ export type Famille = Readonly<{
notionId: string; notionId: string;
@ -13,6 +15,8 @@ export type Famille = Readonly<{
Integration: Date | null; Integration: Date | null;
ContexteEntree: ContexteEntreeDC; ContexteEntree: ContexteEntreeDC;
Sortie: Date | null; Sortie: Date | null;
Penal: StatutPenal;
Social: StatutSocial;
// sorted by date asc // sorted by date asc
Evenements: EvenementFamille[]; Evenements: EvenementFamille[];
DerniereModification: Date; DerniereModification: Date;

14
src/data/StatutPenal.ts Normal file
View file

@ -0,0 +1,14 @@
export const optionsStatutPenal = [
"Aucune poursuite",
"Entendus FO",
"Composition pénale refusée",
"Convoquée CRPC",
"Convoqué devant tribunal collégial",
"Appel de la condamnation",
"Classement sous condition",
"Avertissement pénal probatoire",
"Composition pénale acceptée",
"Classée sans suite",
"Enquête en cours",
] as const;
export type StatutPenal = (typeof optionsStatutPenal)[number];

10
src/data/StatutSocial.ts Normal file
View file

@ -0,0 +1,10 @@
export const optionsStatutSocial = [
"Pas denquête sociale",
"Convoquée par les services sociaux",
"Convocation Juge pour Enfants",
"MJIE",
"Classement après MIJE ou AEMO ou",
"Classement après IP",
"Enquête en cours",
] as const;
export type StatutSocial = (typeof optionsStatutSocial)[number];

View file

@ -3,7 +3,7 @@ export const typesEvenementsSocial = [
"Information préoccupante 1", "Information préoccupante 1",
"Information préoccupante 2", "Information préoccupante 2",
"Information préoccupante 3", "Information préoccupante 3",
"Classement social sans suite", "Classement suite IP",
"Enquête sociale", "Enquête sociale",
"Juge pour enfants", "Juge pour enfants",
"Audition des enfants", "Audition des enfants",

View file

@ -1,39 +1,60 @@
import { GetDatabaseResponse } from "@notionhq/client/build/src/api-endpoints"; import {
DatabaseObjectResponse,
GetDatabaseResponse,
} from "@notionhq/client/build/src/api-endpoints";
import { richTextToPlainText } from "../utils/text/richTextToPlainText";
export function checkDbPropertyOptionsMatchesType( export function checkDbPropertyOptionsMatchesType(
db: GetDatabaseResponse, db: DatabaseObjectResponse,
propName: string, propName: string,
typeOptions: readonly string[] typeOptions: readonly string[]
) { ) {
function diff(list1: readonly string[], list2: readonly string[]): string[] { const prop = db.properties[propName];
const set = new Set(list1); const dbTitle = richTextToPlainText(db.title);
list2.forEach((item) => set.delete(item)); const fullPropName = `"${dbTitle}">"${propName}"`;
return [...set]; if (!prop) {
throw new Error(
`No such prop ${fullPropName}. Existing prop names: ${Object.keys(
db.properties
)}`
);
} }
const dbPropOptions = propOptions(db.properties[propName]); const dbPropOptions = propOptions(prop);
const notInType = diff(dbPropOptions, typeOptions); const notInType = diff(dbPropOptions, typeOptions);
const notInDb = diff(typeOptions, dbPropOptions); const notInDb = diff(typeOptions, dbPropOptions);
if (notInType.length > 0) { if (notInType.length > 0 || notInDb.length > 0) {
console.warn( let message = `DB Property ${fullPropName} schema/types mismatch:`;
`Options for DB Property ${propName} not in Type Options: ${notInType}` if (notInType.length > 0) {
); message += `\n - ${
} notInType.length
} options missing from type: [${notInType.map((i) => `"${i}"`)}]`;
if (notInDb.length > 0) {
console.warn(`Type Options not in for DB Property ${propName}: ${notInDb}`);
}
function propOptions(
prop: GetDatabaseResponse["properties"][string]
): string[] {
if (prop.type === "select") {
return prop.select.options.map((o) => o.name);
} else if (prop.type === "status") {
return prop.status.options.map((o) => o.name);
} else if (prop.type === "multi_select") {
return prop.multi_select.options.map((o) => o.name);
} else {
return [];
} }
if (notInDb.length > 0) {
message += `\n - ${
notInDb.length
} options missing from db: [${notInDb.map((i) => `"${i}"`)}]`;
}
console.warn(message);
} }
} }
function propOptions(
prop: GetDatabaseResponse["properties"][string]
): string[] {
if (prop.type === "select") {
return prop.select.options.map((o) => o.name);
} else if (prop.type === "status") {
return prop.status.options.map((o) => o.name);
} else if (prop.type === "multi_select") {
return prop.multi_select.options.map((o) => o.name);
} else {
return [];
}
}
function diff(list1: readonly string[], list2: readonly string[]): string[] {
const set = new Set(list1);
list2.forEach((item) => set.delete(item));
return [...set];
}

View file

@ -0,0 +1,14 @@
import { Client } from "@notionhq/client";
import { typesEvenements } from "../../data/TypeEvenement";
import { checkDbPropertyOptionsMatchesType } from "./checkDbPropertyOptionsMatchesType";
import { DatabaseObjectResponse } from "@notionhq/client/build/src/api-endpoints";
export async function checkEventDbSchema(
notionClient: Client,
familEventsDbId: string
) {
const eventsDb = (await notionClient.databases.retrieve({
database_id: familEventsDbId,
})) as DatabaseObjectResponse;
checkDbPropertyOptionsMatchesType(eventsDb, "Type", typesEvenements);
}

View file

@ -0,0 +1,27 @@
import { Client } from "@notionhq/client";
import { contexteEntreeDCs } from "../../data/ContexteEntreeDC";
import { statutsFamille } from "../../data/StatutFamille";
import { checkDbPropertyOptionsMatchesType } from "./checkDbPropertyOptionsMatchesType";
import { propContexteEntree, propPenal, propSocial } from "./dbfamilleDesc";
import { optionsStatutSocial } from "../../data/StatutSocial";
import { optionsStatutPenal } from "../../data/StatutPenal";
import { propStatut } from "./dbfamilleDesc";
import { DatabaseObjectResponse } from "@notionhq/client/build/src/api-endpoints";
export async function checkFamilyDbSchema(
notionClient: Client,
familiesDbId: string
) {
const familyDb = (await notionClient.databases.retrieve({
database_id: familiesDbId,
})) as DatabaseObjectResponse;
checkDbPropertyOptionsMatchesType(familyDb, propStatut, statutsFamille);
checkDbPropertyOptionsMatchesType(
familyDb,
propContexteEntree,
contexteEntreeDCs
);
checkDbPropertyOptionsMatchesType(familyDb, propPenal, optionsStatutPenal);
checkDbPropertyOptionsMatchesType(familyDb, propSocial, optionsStatutSocial);
}

View file

@ -0,0 +1 @@
export const familEventsDbId: string = "c4d434b4603c4481a4d445618ecdf999";

View file

@ -0,0 +1,7 @@
export const familiesDbId: string = "5b69e02b296d4a578f8c8ab7fe8b05da";
export const propDerniereModification = "Dernière modification";
export const propSocial = "Social";
export const propPenal = "Pénal";
export const propStatut = "Statut";
export const propContexteEntree = "Contexte dentrée DC";

View file

@ -1,13 +1,10 @@
import { Client, isFullPage } from "@notionhq/client"; import { Client, isFullPage } from "@notionhq/client";
import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints"; import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
import { import { ContexteEntreeDC } from "../../data/ContexteEntreeDC";
ContexteEntreeDC,
contexteEntreeDCs,
} from "../../data/ContexteEntreeDC";
import { EvenementFamille } from "../../data/EvenementFamille"; import { EvenementFamille } from "../../data/EvenementFamille";
import { Famille } from "../../data/Famille"; import { Famille } from "../../data/Famille";
import { StatutFamille, statutsFamille } from "../../data/StatutFamille"; import { StatutFamille } from "../../data/StatutFamille";
import { TypeEvenement, typesEvenements } from "../../data/TypeEvenement"; import { TypeEvenement } from "../../data/TypeEvenement";
import { datePropertyToDate } from "../utils/properties/datePropertyToDate"; import { datePropertyToDate } from "../utils/properties/datePropertyToDate";
import { relationPropertyToPageId } from "../utils/properties/relationPropertyToPageId"; import { relationPropertyToPageId } from "../utils/properties/relationPropertyToPageId";
import { selectPropertyToText } from "../utils/properties/selectPropertyToText"; import { selectPropertyToText } from "../utils/properties/selectPropertyToText";
@ -15,30 +12,25 @@ import { statusPropertyToText } from "../utils/properties/statusPropertyToText";
import { titlePropertyToText } from "../utils/properties/titlePropertyToText"; import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
import { queryAllDbResults } from "../utils/queryAllDbResults"; import { queryAllDbResults } from "../utils/queryAllDbResults";
import { richTextPropertyToPlainText } from "../utils/text/richTextPropertyToPlainText"; import { richTextPropertyToPlainText } from "../utils/text/richTextPropertyToPlainText";
import { checkDbPropertyOptionsMatchesType } from "./checkDbPropertyOptionsMatchesType"; import { familEventsDbId } from "./dbIds";
import {
const contexteEntreeDCPropName = "Contexte dentrée DC"; propContexteEntree,
familiesDbId,
propDerniereModification,
propPenal,
propSocial,
} from "./dbfamilleDesc";
import { checkFamilyDbSchema } from "./checkFamilyDbSchema";
import { checkEventDbSchema } from "./checkEventDbSchema";
import { StatutPenal } from "../../data/StatutPenal";
import { StatutSocial } from "../../data/StatutSocial";
export async function fetchFamiliesWithEventsFromNotion( export async function fetchFamiliesWithEventsFromNotion(
notionClient: Client notionClient: Client
): Promise<Famille[]> { ): Promise<Famille[]> {
const familiesDbId: string = "5b69e02b296d4a578f8c8ab7fe8b05da"; await checkFamilyDbSchema(notionClient, familiesDbId);
const familEventsDbId: string = "c4d434b4603c4481a4d445618ecdf999";
const familyDb = await notionClient.databases.retrieve({ await checkEventDbSchema(notionClient, familEventsDbId);
database_id: familiesDbId,
});
checkDbPropertyOptionsMatchesType(familyDb, "Statut", statutsFamille);
checkDbPropertyOptionsMatchesType(
familyDb,
contexteEntreeDCPropName,
contexteEntreeDCs
);
const eventsDb = await notionClient.databases.retrieve({
database_id: familEventsDbId,
});
checkDbPropertyOptionsMatchesType(eventsDb, "Type", typesEvenements);
const eventPages = ( const eventPages = (
await queryAllDbResults(notionClient, { await queryAllDbResults(notionClient, {
@ -95,15 +87,17 @@ function buildFamily(
Statut: statusPropertyToText(pageProperties, "Statut") as StatutFamille, Statut: statusPropertyToText(pageProperties, "Statut") as StatutFamille,
ContexteEntree: selectPropertyToText( ContexteEntree: selectPropertyToText(
pageProperties, pageProperties,
contexteEntreeDCPropName propContexteEntree
) as ContexteEntreeDC, ) as ContexteEntreeDC,
Integration: datePropertyToDate(pageProperties, "Intégration"), Integration: datePropertyToDate(pageProperties, "Intégration"),
Sortie: datePropertyToDate(pageProperties, "Sortie"), Sortie: datePropertyToDate(pageProperties, "Sortie"),
Evenements: familyEvents.filter((fe) => fe.notionIdFamille === page.id), Evenements: familyEvents.filter((fe) => fe.notionIdFamille === page.id),
DerniereModification: datePropertyToDate( DerniereModification: datePropertyToDate(
pageProperties, pageProperties,
"Dernière modification" propDerniereModification
)!, )!,
Penal: statusPropertyToText(pageProperties, propPenal) as StatutPenal,
Social: statusPropertyToText(pageProperties, propSocial) as StatutSocial,
}; };
return family; return family;
} }

View file

@ -15,31 +15,6 @@ export function computeELStatsAtDate(
isResistant(famille, asOfDate) || isExResistant(famille, asOfDate) isResistant(famille, asOfDate) || isExResistant(famille, asOfDate)
); );
const famillesAvecProcedureCivile = familleResistantesOrEx.filter((famille) =>
famille.Evenements.find(
(evt) => isProcedureCivile(evt) && isEvenementBefore(evt, asOfDate)
)
);
const famillesAvecClasseesSocialSansSuite = familleResistantesOrEx.filter(
(famille) =>
famille.Evenements.find(
(evt) =>
isProcedureCivile(evt) &&
isEvenementBefore(evt, asOfDate) &&
evt.Type === "Classement social sans suite"
)
);
const famillesAvecJugeDesEnfants = familleResistantesOrEx.filter((famille) =>
famille.Evenements.find(
(evt) =>
isProcedureCivile(evt) &&
isEvenementBefore(evt, asOfDate) &&
evt.Type === "Juge pour enfants"
)
);
const famillesAvecContrôleFiscal = familleResistantesOrEx.filter((f) => const famillesAvecContrôleFiscal = familleResistantesOrEx.filter((f) =>
f.Evenements.find((e) => e.Type === "Contrôle fiscal") f.Evenements.find((e) => e.Type === "Contrôle fiscal")
); );

View file

@ -6,8 +6,8 @@ export const statsSocialesDesc = {
nbFamillesProcedureCivile: { nbFamillesProcedureCivile: {
label: "Nb Familles avec une procedure sociale", label: "Nb Familles avec une procedure sociale",
}, },
nbFamilleAvecClassementSansSuite: { nbFamilleAvecClassementSuiteIP: {
label: "Nb Familles avec un classement sans suite", label: "Nb Familles avec un classement suite IP (evenements)",
}, },
nbFamilleAvecIP: { nbFamilleAvecIP: {
@ -32,6 +32,32 @@ export const statsSocialesDesc = {
}, },
}, },
}, },
propFamilleSocial: {
label: "Stats Familles basé sur la Propriété Social",
stats: {
pasDenquete: {
label: "Pas denquête sociale",
},
convoqueeServiceSociaux: {
label: "Convoquée par les services sociaux",
},
convocationJugePourEnfants: {
label: "Convocation Juge pour Enfants",
},
mije: {
label: "MIJE",
},
classementApresMijeOuAemo: {
label: "Classement après MIJE ou AEMO ou",
},
classementApresIP: {
label: "Classement après IP",
},
enqueteEnCours: {
label: "Enquête en cours",
},
},
},
}, },
} as const; } as const;

View file

@ -39,10 +39,29 @@ export function computeStatsSociales(familles: Famille[]): StatsSociales {
(e) => e.Type === "Juge pour enfants" && !isEvenementBefore(e, now) (e) => e.Type === "Juge pour enfants" && !isEvenementBefore(e, now)
).length, ).length,
}, },
nbFamilleAvecClassementSansSuite: filterFamillesWithOneOfEvenementsOfType( nbFamilleAvecClassementSuiteIP: filterFamillesWithOneOfEvenementsOfType(
familles, familles,
"Classement social sans suite" "Classement suite IP"
).length, ).length,
propFamilleSocial: {
classementApresIP: familles.filter(
(f) => f.Social === "Classement après IP"
).length,
classementApresMijeOuAemo: familles.filter(
(f) => f.Social === "Classement après MIJE ou AEMO ou"
).length,
convocationJugePourEnfants: familles.filter(
(f) => f.Social === "Convocation Juge pour Enfants"
).length,
convoqueeServiceSociaux: familles.filter(
(f) => f.Social === "Convoquée par les services sociaux"
).length,
enqueteEnCours: familles.filter((f) => f.Social === "Enquête en cours")
.length,
mije: familles.filter((f) => f.Social === "MJIE").length,
pasDenquete: familles.filter((f) => f.Social === "Pas denquête sociale")
.length,
},
}; };
return statsCiviles; return statsCiviles;
} }