feat: stats social basé sur propriété

wip-related-pages
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 { EvenementFamille } from "./EvenementFamille";
import { StatutFamille } from "./StatutFamille";
import { StatutSocial } from "./StatutSocial";
import { StatutPenal } from "./StatutPenal";
export type Famille = Readonly<{
notionId: string;
@ -13,6 +15,8 @@ export type Famille = Readonly<{
Integration: Date | null;
ContexteEntree: ContexteEntreeDC;
Sortie: Date | null;
Penal: StatutPenal;
Social: StatutSocial;
// sorted by date asc
Evenements: EvenementFamille[];
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 2",
"Information préoccupante 3",
"Classement social sans suite",
"Classement suite IP",
"Enquête sociale",
"Juge pour 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(
db: GetDatabaseResponse,
db: DatabaseObjectResponse,
propName: string,
typeOptions: readonly string[]
) {
function diff(list1: readonly string[], list2: readonly string[]): string[] {
const set = new Set(list1);
list2.forEach((item) => set.delete(item));
return [...set];
const prop = db.properties[propName];
const dbTitle = richTextToPlainText(db.title);
const fullPropName = `"${dbTitle}">"${propName}"`;
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 notInDb = diff(typeOptions, dbPropOptions);
if (notInType.length > 0) {
console.warn(
`Options for DB Property ${propName} not in Type Options: ${notInType}`
);
}
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 (notInType.length > 0 || notInDb.length > 0) {
let message = `DB Property ${fullPropName} schema/types mismatch:`;
if (notInType.length > 0) {
message += `\n - ${
notInType.length
} options missing from type: [${notInType.map((i) => `"${i}"`)}]`;
}
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 { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
import {
ContexteEntreeDC,
contexteEntreeDCs,
} from "../../data/ContexteEntreeDC";
import { ContexteEntreeDC } from "../../data/ContexteEntreeDC";
import { EvenementFamille } from "../../data/EvenementFamille";
import { Famille } from "../../data/Famille";
import { StatutFamille, statutsFamille } from "../../data/StatutFamille";
import { TypeEvenement, typesEvenements } from "../../data/TypeEvenement";
import { StatutFamille } from "../../data/StatutFamille";
import { TypeEvenement } from "../../data/TypeEvenement";
import { datePropertyToDate } from "../utils/properties/datePropertyToDate";
import { relationPropertyToPageId } from "../utils/properties/relationPropertyToPageId";
import { selectPropertyToText } from "../utils/properties/selectPropertyToText";
@ -15,30 +12,25 @@ import { statusPropertyToText } from "../utils/properties/statusPropertyToText";
import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
import { queryAllDbResults } from "../utils/queryAllDbResults";
import { richTextPropertyToPlainText } from "../utils/text/richTextPropertyToPlainText";
import { checkDbPropertyOptionsMatchesType } from "./checkDbPropertyOptionsMatchesType";
const contexteEntreeDCPropName = "Contexte dentrée DC";
import { familEventsDbId } from "./dbIds";
import {
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(
notionClient: Client
): Promise<Famille[]> {
const familiesDbId: string = "5b69e02b296d4a578f8c8ab7fe8b05da";
const familEventsDbId: string = "c4d434b4603c4481a4d445618ecdf999";
await checkFamilyDbSchema(notionClient, familiesDbId);
const familyDb = await notionClient.databases.retrieve({
database_id: familiesDbId,
});
checkDbPropertyOptionsMatchesType(familyDb, "Statut", statutsFamille);
checkDbPropertyOptionsMatchesType(
familyDb,
contexteEntreeDCPropName,
contexteEntreeDCs
);
const eventsDb = await notionClient.databases.retrieve({
database_id: familEventsDbId,
});
checkDbPropertyOptionsMatchesType(eventsDb, "Type", typesEvenements);
await checkEventDbSchema(notionClient, familEventsDbId);
const eventPages = (
await queryAllDbResults(notionClient, {
@ -95,15 +87,17 @@ function buildFamily(
Statut: statusPropertyToText(pageProperties, "Statut") as StatutFamille,
ContexteEntree: selectPropertyToText(
pageProperties,
contexteEntreeDCPropName
propContexteEntree
) as ContexteEntreeDC,
Integration: datePropertyToDate(pageProperties, "Intégration"),
Sortie: datePropertyToDate(pageProperties, "Sortie"),
Evenements: familyEvents.filter((fe) => fe.notionIdFamille === page.id),
DerniereModification: datePropertyToDate(
pageProperties,
"Dernière modification"
propDerniereModification
)!,
Penal: statusPropertyToText(pageProperties, propPenal) as StatutPenal,
Social: statusPropertyToText(pageProperties, propSocial) as StatutSocial,
};
return family;
}

View File

@ -15,31 +15,6 @@ export function computeELStatsAtDate(
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) =>
f.Evenements.find((e) => e.Type === "Contrôle fiscal")
);

View File

@ -6,8 +6,8 @@ export const statsSocialesDesc = {
nbFamillesProcedureCivile: {
label: "Nb Familles avec une procedure sociale",
},
nbFamilleAvecClassementSansSuite: {
label: "Nb Familles avec un classement sans suite",
nbFamilleAvecClassementSuiteIP: {
label: "Nb Familles avec un classement suite IP (evenements)",
},
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;

View File

@ -39,10 +39,29 @@ export function computeStatsSociales(familles: Famille[]): StatsSociales {
(e) => e.Type === "Juge pour enfants" && !isEvenementBefore(e, now)
).length,
},
nbFamilleAvecClassementSansSuite: filterFamillesWithOneOfEvenementsOfType(
nbFamilleAvecClassementSuiteIP: filterFamillesWithOneOfEvenementsOfType(
familles,
"Classement social sans suite"
"Classement suite IP"
).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;
}