mirror of
https://framagit.org/enfance-libre/statistiques
synced 2025-12-07 23:13:45 +00:00
feat: stats social basé sur propriété
This commit is contained in:
parent
80172ebb11
commit
8dda887349
13 changed files with 196 additions and 84 deletions
|
|
@ -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
14
src/data/StatutPenal.ts
Normal 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
10
src/data/StatutSocial.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
export const optionsStatutSocial = [
|
||||||
|
"Pas d’enquê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];
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
}
|
||||||
|
|
|
||||||
14
src/notion/fetch/checkEventDbSchema.ts
Normal file
14
src/notion/fetch/checkEventDbSchema.ts
Normal 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);
|
||||||
|
}
|
||||||
27
src/notion/fetch/checkFamilyDbSchema.ts
Normal file
27
src/notion/fetch/checkFamilyDbSchema.ts
Normal 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);
|
||||||
|
}
|
||||||
1
src/notion/fetch/dbIds.ts
Normal file
1
src/notion/fetch/dbIds.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const familEventsDbId: string = "c4d434b4603c4481a4d445618ecdf999";
|
||||||
7
src/notion/fetch/dbfamilleDesc.ts
Normal file
7
src/notion/fetch/dbfamilleDesc.ts
Normal 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 d’entrée DC";
|
||||||
|
|
@ -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 d’entré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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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 d’enquê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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 d’enquête sociale")
|
||||||
|
.length,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
return statsCiviles;
|
return statsCiviles;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue