mirror of
https://framagit.org/enfance-libre/statistiques
synced 2025-12-07 04:03:44 +00:00
feat: stats departementales tabulaire
This commit is contained in:
parent
8d64d79983
commit
c82ae0833b
12 changed files with 566 additions and 285 deletions
4
src/data/Departement.ts
Normal file
4
src/data/Departement.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export type Departement = {
|
||||
notionId: string;
|
||||
name: string;
|
||||
};
|
||||
22
src/index.ts
22
src/index.ts
|
|
@ -1,5 +1,5 @@
|
|||
import { Client } from "@notionhq/client";
|
||||
import { writeFileSync } from "fs";
|
||||
import { stat, Stats, writeFileSync } from "fs";
|
||||
import { fetchFamiliesWithEventsFromNotion } from "./notion/fetch/fetchFamiliesWithEventsFromNotion";
|
||||
import { publishStatsToPage } from "./notion/publish/v2/publishStatsToPage";
|
||||
import { computeStatsPenales } from "./statistiques/v2/penales/computeStatsPenales";
|
||||
|
|
@ -19,6 +19,8 @@ import { nettoyerDonneesFamilles } from "./data/nettoyage/familles/preparerDonne
|
|||
import { statsAutresDesc } from "./statistiques/v2/autres/StatsAutres";
|
||||
import { computeStatsAutres } from "./statistiques/v2/autres/computeStatsAutres";
|
||||
import { updateUpdateDate as updateRootPageUpdateDate } from "./notion/publish/updateUpdateDate";
|
||||
import { computeStatsDepartementales } from "./statistiques/v2/departemental/computeStatDepartement";
|
||||
import { publishStatsDepartementales } from "./notion/publish/publishStatsDepartementales";
|
||||
|
||||
type ProcessOptions = {
|
||||
dryRun: boolean;
|
||||
|
|
@ -51,12 +53,12 @@ function buildProcessOptions(): ProcessOptions {
|
|||
await checkDbSchemas(notionClient);
|
||||
|
||||
console.log("Téléchargement des données...");
|
||||
const donneesFamillesBrutes = await fetchFamiliesWithEventsFromNotion(
|
||||
notionClient
|
||||
);
|
||||
const donneesNotion = await fetchFamiliesWithEventsFromNotion(notionClient);
|
||||
|
||||
console.log("Nettoyage des données brutes...");
|
||||
const { familles, messages } = nettoyerDonneesFamilles(donneesFamillesBrutes);
|
||||
const { familles, messages } = nettoyerDonneesFamilles(
|
||||
donneesNotion.familles
|
||||
);
|
||||
|
||||
const donneesBloquantes = messages.filter(
|
||||
(m) => m.severite === "Donnée incohérente bloquante"
|
||||
|
|
@ -107,6 +109,10 @@ function buildProcessOptions(): ProcessOptions {
|
|||
const statsSociales = computeStatsSociales(familles);
|
||||
const statsAutres = computeStatsAutres(familles);
|
||||
const statsGeneralesMensuelles = computeStatsGeneralesMensuelles(familles);
|
||||
const statsDepartementales = computeStatsDepartementales(
|
||||
familles,
|
||||
donneesNotion.departements
|
||||
);
|
||||
const mermaidDiagramStatsGeneralesMensuellesCode =
|
||||
mermaidDiagramStatsGeneralesMensuelles(statsGeneralesMensuelles);
|
||||
writeFileSync(
|
||||
|
|
@ -131,6 +137,7 @@ function buildProcessOptions(): ProcessOptions {
|
|||
sociales: statsSociales,
|
||||
statsAutres: statsAutres,
|
||||
StatsGeneralesMensuelles: statsGeneralesMensuelles,
|
||||
statsDepartementales: statsDepartementales,
|
||||
},
|
||||
null,
|
||||
" "
|
||||
|
|
@ -177,5 +184,10 @@ function buildProcessOptions(): ProcessOptions {
|
|||
statsAutresDesc,
|
||||
statsAutres
|
||||
);
|
||||
await publishStatsDepartementales(
|
||||
notionClient,
|
||||
"21a168be9f1980a7a61df867770483c3",
|
||||
statsDepartementales
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
40
src/notion/fetch/fetchContacts.ts
Normal file
40
src/notion/fetch/fetchContacts.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import { Client, isFullPage } from "@notionhq/client";
|
||||
import { Contact } from "../../data/Contact";
|
||||
import { checkboxPropertyToBoolean } from "../utils/properties/checkboxPropertyToBoolean";
|
||||
import { relationPropertyToPageId } from "../utils/properties/relationPropertyToPageId";
|
||||
import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
|
||||
import { queryAllDbResults } from "../utils/queryAllDbResults";
|
||||
import { contactsDbId } from "./dbIds";
|
||||
|
||||
export async function fetchContacts(
|
||||
notionClient: Client,
|
||||
cacheConfig: boolean | { ttl: number },
|
||||
missions: Readonly<{
|
||||
notionId: string;
|
||||
Nom: string;
|
||||
Equipe: string;
|
||||
ContactsNotionIds: string[];
|
||||
}>[]
|
||||
) {
|
||||
const contactPages = (
|
||||
await queryAllDbResults(
|
||||
notionClient,
|
||||
{
|
||||
database_id: contactsDbId,
|
||||
},
|
||||
{ cache: cacheConfig }
|
||||
)
|
||||
).filter(isFullPage);
|
||||
|
||||
const contacts: Contact[] = contactPages.map((page) => ({
|
||||
notionId: page.id,
|
||||
notionIdFamille: relationPropertyToPageId(page.properties, "Famille")!,
|
||||
Nom: titlePropertyToText(page.properties, "Nom"),
|
||||
AExercéUneMission: checkboxPropertyToBoolean(
|
||||
page.properties,
|
||||
"A exercé une mission"
|
||||
),
|
||||
Missions: missions.filter((m) => m.ContactsNotionIds.includes(page.id)),
|
||||
}));
|
||||
return contacts;
|
||||
}
|
||||
26
src/notion/fetch/fetchDepartements.ts
Normal file
26
src/notion/fetch/fetchDepartements.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { Client, isFullPage } from "@notionhq/client";
|
||||
import { Departement } from "../../data/Departement";
|
||||
import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
|
||||
import { queryAllDbResults } from "../utils/queryAllDbResults";
|
||||
import { departementsDbId } from "./dbIds";
|
||||
|
||||
export async function fetchDepartements(
|
||||
notionClient: Client,
|
||||
cacheConfig: boolean | { ttl: number }
|
||||
) {
|
||||
const departementPages = (
|
||||
await queryAllDbResults(
|
||||
notionClient,
|
||||
{
|
||||
database_id: departementsDbId,
|
||||
},
|
||||
{ cache: cacheConfig }
|
||||
)
|
||||
).filter(isFullPage);
|
||||
|
||||
const departements: Departement[] = departementPages.map((page) => ({
|
||||
notionId: page.id,
|
||||
name: titlePropertyToText(page.properties, "Nom"),
|
||||
}));
|
||||
return departements;
|
||||
}
|
||||
53
src/notion/fetch/fetchEvenements.ts
Normal file
53
src/notion/fetch/fetchEvenements.ts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
import { Client, isFullPage } from "@notionhq/client";
|
||||
import { Amende } from "../../data/Amende";
|
||||
import { queryAllDbResults } from "../utils/queryAllDbResults";
|
||||
import { familEventsDbId } from "./dbIds";
|
||||
import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
|
||||
import { EvenementFamille } from "../../data/EvenementFamille";
|
||||
import { TypeEvenement } from "../../data/TypeEvenement";
|
||||
import { datePropertyToDate } from "../utils/properties/datePropertyToDate";
|
||||
import { relationPropertyToPageId } from "../utils/properties/relationPropertyToPageId";
|
||||
import { selectPropertyToText } from "../utils/properties/selectPropertyToText";
|
||||
import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
|
||||
import { richTextPropertyToPlainText } from "../utils/text/richTextPropertyToPlainText";
|
||||
|
||||
export async function fetchEvenements(
|
||||
notionClient: Client,
|
||||
cacheConfig: boolean | { ttl: number },
|
||||
amendes: Amende[]
|
||||
) {
|
||||
const eventPages = (
|
||||
await queryAllDbResults(
|
||||
notionClient,
|
||||
{
|
||||
database_id: familEventsDbId,
|
||||
sorts: [{ property: "Date", direction: "ascending" }],
|
||||
},
|
||||
{ cache: cacheConfig }
|
||||
)
|
||||
).filter(isFullPage);
|
||||
const familyEvents = eventPages.map((pageObjectResponse) => {
|
||||
return buildFamilyEvent(pageObjectResponse, amendes);
|
||||
});
|
||||
return familyEvents;
|
||||
}
|
||||
export function buildFamilyEvent(
|
||||
page: PageObjectResponse,
|
||||
amendes: Amende[]
|
||||
): EvenementFamille {
|
||||
const pageProperties = page.properties;
|
||||
|
||||
const familyEvent: EvenementFamille = {
|
||||
notionId: page.id,
|
||||
Évènement: titlePropertyToText(pageProperties, "Évènement"),
|
||||
Type: selectPropertyToText(pageProperties, "Type")! as TypeEvenement,
|
||||
"Enfants concernés": richTextPropertyToPlainText(
|
||||
pageProperties,
|
||||
"Enfants concernés"
|
||||
),
|
||||
Date: datePropertyToDate(pageProperties, "Date"),
|
||||
notionIdFamille: relationPropertyToPageId(pageProperties, "Famille")!,
|
||||
Amendes: amendes.filter((a) => a.notionIdEvenement === page.id),
|
||||
};
|
||||
return familyEvent;
|
||||
}
|
||||
|
|
@ -1,47 +1,26 @@
|
|||
import { Client, isFullPage } from "@notionhq/client";
|
||||
import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
|
||||
import { ContexteEntreeDC } from "../../data/ContexteEntreeDC";
|
||||
import { EvenementFamille } from "../../data/EvenementFamille";
|
||||
import { Client } from "@notionhq/client";
|
||||
import { Famille } from "../../data/Famille";
|
||||
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";
|
||||
import { statusPropertyToText } from "../utils/properties/statusPropertyToText";
|
||||
import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
|
||||
import { CacheConfig, queryAllDbResults } from "../utils/queryAllDbResults";
|
||||
import { richTextPropertyToPlainText } from "../utils/text/richTextPropertyToPlainText";
|
||||
import {
|
||||
contactsDbId,
|
||||
departementsDbId,
|
||||
familEventsDbId,
|
||||
missionsDbId,
|
||||
} from "./dbIds";
|
||||
import {
|
||||
propContexteEntree,
|
||||
familiesDbId,
|
||||
propDerniereModification,
|
||||
propPenal,
|
||||
propSocial,
|
||||
} from "./dbfamilleDesc";
|
||||
import { StatutPenal } from "../../data/StatutPenal";
|
||||
import { StatutSocial } from "../../data/StatutSocial";
|
||||
import { CacheConfig } from "../utils/queryAllDbResults";
|
||||
import { Contact } from "../../data/Contact";
|
||||
import { Mission } from "../../data/Mission";
|
||||
import { relationPropertyToPageIds } from "../utils/properties/relationPropertyToPageIds";
|
||||
import { checkboxPropertyToBoolean } from "../utils/properties/checkboxPropertyToBoolean";
|
||||
import { Amende } from "../../data/Amende";
|
||||
import { fetchAmendes } from "./fetchAmendes";
|
||||
import { Departement } from "../../data/Departement";
|
||||
import { fetchEvenements } from "./fetchEvenements";
|
||||
import { fetchContacts } from "./fetchContacts";
|
||||
import { fetchMissions } from "./fetchMissions";
|
||||
import { fetchDepartements } from "./fetchDepartements";
|
||||
import { fetchFamilles } from "./fetchFamilles";
|
||||
|
||||
type Departement = {
|
||||
notionId: string;
|
||||
name: string;
|
||||
export type FetchedData = {
|
||||
familles: Famille[];
|
||||
departements: Departement[];
|
||||
};
|
||||
|
||||
export async function fetchFamiliesWithEventsFromNotion(
|
||||
notionClient: Client,
|
||||
cacheConfig: CacheConfig = { ttl: 3600 * 1000 }
|
||||
): Promise<Famille[]> {
|
||||
): Promise<FetchedData> {
|
||||
const departements: Departement[] = await fetchDepartements(
|
||||
notionClient,
|
||||
cacheConfig
|
||||
|
|
@ -61,213 +40,5 @@ export async function fetchFamiliesWithEventsFromNotion(
|
|||
departements,
|
||||
contacts
|
||||
);
|
||||
return familles;
|
||||
}
|
||||
|
||||
async function fetchFamilles(
|
||||
notionClient: Client,
|
||||
cacheConfig: boolean | { ttl: number },
|
||||
familyEvents: Readonly<{
|
||||
notionId: string;
|
||||
notionIdFamille: string;
|
||||
Évènement: string;
|
||||
Date: Date | null;
|
||||
Type: TypeEvenement;
|
||||
Amendes: Amende[];
|
||||
"Enfants concernés": string;
|
||||
}>[],
|
||||
departements: Departement[],
|
||||
contacts: Readonly<{
|
||||
notionId: string;
|
||||
notionIdFamille: string;
|
||||
Nom: string;
|
||||
Missions: Mission[];
|
||||
AExercéUneMission: boolean;
|
||||
}>[]
|
||||
) {
|
||||
const familyPages = (
|
||||
await queryAllDbResults(
|
||||
notionClient,
|
||||
{
|
||||
database_id: familiesDbId,
|
||||
},
|
||||
{ cache: cacheConfig }
|
||||
)
|
||||
).filter(isFullPage);
|
||||
const familles: Famille[] = await Promise.all(
|
||||
familyPages.map((pageObjectResponse) => {
|
||||
return buildFamily(
|
||||
pageObjectResponse,
|
||||
familyEvents,
|
||||
departements,
|
||||
contacts
|
||||
);
|
||||
})
|
||||
);
|
||||
return familles;
|
||||
}
|
||||
|
||||
async function fetchEvenements(
|
||||
notionClient: Client,
|
||||
cacheConfig: boolean | { ttl: number },
|
||||
amendes: Amende[]
|
||||
) {
|
||||
const eventPages = (
|
||||
await queryAllDbResults(
|
||||
notionClient,
|
||||
{
|
||||
database_id: familEventsDbId,
|
||||
sorts: [{ property: "Date", direction: "ascending" }],
|
||||
},
|
||||
{ cache: cacheConfig }
|
||||
)
|
||||
).filter(isFullPage);
|
||||
const familyEvents = eventPages.map((pageObjectResponse) => {
|
||||
return buildFamilyEvent(pageObjectResponse, amendes);
|
||||
});
|
||||
return familyEvents;
|
||||
}
|
||||
|
||||
async function fetchContacts(
|
||||
notionClient: Client,
|
||||
cacheConfig: boolean | { ttl: number },
|
||||
missions: Readonly<{
|
||||
notionId: string;
|
||||
Nom: string;
|
||||
Equipe: string;
|
||||
ContactsNotionIds: string[];
|
||||
}>[]
|
||||
) {
|
||||
const contactPages = (
|
||||
await queryAllDbResults(
|
||||
notionClient,
|
||||
{
|
||||
database_id: contactsDbId,
|
||||
},
|
||||
{ cache: cacheConfig }
|
||||
)
|
||||
).filter(isFullPage);
|
||||
|
||||
const contacts: Contact[] = contactPages.map((page) => ({
|
||||
notionId: page.id,
|
||||
notionIdFamille: relationPropertyToPageId(page.properties, "Famille")!,
|
||||
Nom: titlePropertyToText(page.properties, "Nom"),
|
||||
AExercéUneMission: checkboxPropertyToBoolean(
|
||||
page.properties,
|
||||
"A exercé une mission"
|
||||
),
|
||||
Missions: missions.filter((m) => m.ContactsNotionIds.includes(page.id)),
|
||||
}));
|
||||
return contacts;
|
||||
}
|
||||
|
||||
async function fetchMissions(
|
||||
notionClient: Client,
|
||||
cacheConfig: boolean | { ttl: number }
|
||||
) {
|
||||
const missionsPages = (
|
||||
await queryAllDbResults(
|
||||
notionClient,
|
||||
{
|
||||
database_id: missionsDbId,
|
||||
},
|
||||
{ cache: cacheConfig }
|
||||
)
|
||||
).filter(isFullPage);
|
||||
const missions: Mission[] = missionsPages.map((page) => ({
|
||||
notionId: page.id,
|
||||
Nom: titlePropertyToText(page.properties, "Nom"),
|
||||
Equipe: selectPropertyToText(page.properties, "Équipe")!,
|
||||
ContactsNotionIds: relationPropertyToPageIds(
|
||||
page.properties,
|
||||
"📔 Contacts"
|
||||
),
|
||||
}));
|
||||
return missions;
|
||||
}
|
||||
|
||||
async function fetchDepartements(
|
||||
notionClient: Client,
|
||||
cacheConfig: boolean | { ttl: number }
|
||||
) {
|
||||
const departementPages = (
|
||||
await queryAllDbResults(
|
||||
notionClient,
|
||||
{
|
||||
database_id: departementsDbId,
|
||||
},
|
||||
{ cache: cacheConfig }
|
||||
)
|
||||
).filter(isFullPage);
|
||||
|
||||
const departements: Departement[] = departementPages.map((page) => ({
|
||||
notionId: page.id,
|
||||
name: titlePropertyToText(page.properties, "Nom"),
|
||||
}));
|
||||
return departements;
|
||||
}
|
||||
|
||||
function buildFamilyEvent(
|
||||
page: PageObjectResponse,
|
||||
amendes: Amende[]
|
||||
): EvenementFamille {
|
||||
const pageProperties = page.properties;
|
||||
|
||||
const familyEvent: EvenementFamille = {
|
||||
notionId: page.id,
|
||||
Évènement: titlePropertyToText(pageProperties, "Évènement"),
|
||||
Type: selectPropertyToText(pageProperties, "Type")! as TypeEvenement,
|
||||
"Enfants concernés": richTextPropertyToPlainText(
|
||||
pageProperties,
|
||||
"Enfants concernés"
|
||||
),
|
||||
Date: datePropertyToDate(pageProperties, "Date"),
|
||||
notionIdFamille: relationPropertyToPageId(pageProperties, "Famille")!,
|
||||
Amendes: amendes.filter((a) => a.notionIdEvenement === page.id),
|
||||
};
|
||||
return familyEvent;
|
||||
}
|
||||
|
||||
function buildFamily(
|
||||
page: PageObjectResponse,
|
||||
familyEvents: EvenementFamille[],
|
||||
departements: Departement[],
|
||||
contacts: Contact[]
|
||||
): Famille {
|
||||
const pageProperties = page.properties;
|
||||
|
||||
const departementId = relationPropertyToPageId(pageProperties, "Département");
|
||||
const departement = departementId
|
||||
? departements.find((d) => d.notionId === departementId)
|
||||
: null;
|
||||
|
||||
const family: Famille = {
|
||||
notionId: page.id,
|
||||
Titre: titlePropertyToText(pageProperties, ""),
|
||||
Statut: statusPropertyToText(pageProperties, "Statut") as StatutFamille,
|
||||
ContexteEntree: selectPropertyToText(
|
||||
pageProperties,
|
||||
propContexteEntree
|
||||
) as ContexteEntreeDC,
|
||||
Integration: datePropertyToDate(pageProperties, "Intégration"),
|
||||
Sortie: datePropertyToDate(pageProperties, "Sortie"),
|
||||
Evenements: familyEvents.filter((fe) => fe.notionIdFamille === page.id),
|
||||
// Ces 4 propriétés seront peuplés après le data consistency check
|
||||
EvenementsDates: [],
|
||||
EvenementsEL: [],
|
||||
EvenementsAvantEL: [],
|
||||
EvenementsApresEL: [],
|
||||
Departement: departement?.name || null,
|
||||
DerniereModification: datePropertyToDate(
|
||||
pageProperties,
|
||||
propDerniereModification
|
||||
)!,
|
||||
Penal: statusPropertyToText(pageProperties, propPenal) as StatutPenal,
|
||||
Social: statusPropertyToText(pageProperties, propSocial) as StatutSocial,
|
||||
Missions: contacts
|
||||
.filter((c) => c.notionIdFamille === page.id)
|
||||
.flatMap((c) => c.Missions),
|
||||
Contacts: contacts.filter((c) => c.notionIdFamille === page.id),
|
||||
};
|
||||
return family;
|
||||
return { familles, departements };
|
||||
}
|
||||
|
|
|
|||
112
src/notion/fetch/fetchFamilles.ts
Normal file
112
src/notion/fetch/fetchFamilles.ts
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
import { Client, isFullPage } from "@notionhq/client";
|
||||
import { Amende } from "../../data/Amende";
|
||||
import { Departement } from "../../data/Departement";
|
||||
import { Famille } from "../../data/Famille";
|
||||
import { Mission } from "../../data/Mission";
|
||||
import { TypeEvenement } from "../../data/TypeEvenement";
|
||||
import { queryAllDbResults } from "../utils/queryAllDbResults";
|
||||
import {
|
||||
familiesDbId,
|
||||
propContexteEntree,
|
||||
propDerniereModification,
|
||||
propPenal,
|
||||
propSocial,
|
||||
} from "./dbfamilleDesc";
|
||||
import { PageObjectResponse } from "@notionhq/client/build/src/api-endpoints";
|
||||
import { EvenementFamille } from "../../data/EvenementFamille";
|
||||
import { Contact } from "../../data/Contact";
|
||||
import { relationPropertyToPageId } from "../utils/properties/relationPropertyToPageId";
|
||||
import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
|
||||
import { statusPropertyToText } from "../utils/properties/statusPropertyToText";
|
||||
import { selectPropertyToText } from "../utils/properties/selectPropertyToText";
|
||||
import { StatutFamille } from "../../data/StatutFamille";
|
||||
import { ContexteEntreeDC } from "../../data/ContexteEntreeDC";
|
||||
import { datePropertyToDate } from "../utils/properties/datePropertyToDate";
|
||||
import { StatutPenal } from "../../data/StatutPenal";
|
||||
import { StatutSocial } from "../../data/StatutSocial";
|
||||
|
||||
export async function fetchFamilles(
|
||||
notionClient: Client,
|
||||
cacheConfig: boolean | { ttl: number },
|
||||
familyEvents: Readonly<{
|
||||
notionId: string;
|
||||
notionIdFamille: string;
|
||||
Évènement: string;
|
||||
Date: Date | null;
|
||||
Type: TypeEvenement;
|
||||
Amendes: Amende[];
|
||||
"Enfants concernés": string;
|
||||
}>[],
|
||||
departements: Departement[],
|
||||
contacts: Readonly<{
|
||||
notionId: string;
|
||||
notionIdFamille: string;
|
||||
Nom: string;
|
||||
Missions: Mission[];
|
||||
AExercéUneMission: boolean;
|
||||
}>[]
|
||||
) {
|
||||
const familyPages = (
|
||||
await queryAllDbResults(
|
||||
notionClient,
|
||||
{
|
||||
database_id: familiesDbId,
|
||||
},
|
||||
{ cache: cacheConfig }
|
||||
)
|
||||
).filter(isFullPage);
|
||||
const familles: Famille[] = await Promise.all(
|
||||
familyPages.map((pageObjectResponse) => {
|
||||
return buildFamily(
|
||||
pageObjectResponse,
|
||||
familyEvents,
|
||||
departements,
|
||||
contacts
|
||||
);
|
||||
})
|
||||
);
|
||||
return familles;
|
||||
}
|
||||
function buildFamily(
|
||||
page: PageObjectResponse,
|
||||
familyEvents: EvenementFamille[],
|
||||
departements: Departement[],
|
||||
contacts: Contact[]
|
||||
): Famille {
|
||||
const pageProperties = page.properties;
|
||||
|
||||
const departementId = relationPropertyToPageId(pageProperties, "Département");
|
||||
const departement = departementId
|
||||
? departements.find((d) => d.notionId === departementId)
|
||||
: null;
|
||||
|
||||
const family: Famille = {
|
||||
notionId: page.id,
|
||||
Titre: titlePropertyToText(pageProperties, ""),
|
||||
Statut: statusPropertyToText(pageProperties, "Statut") as StatutFamille,
|
||||
ContexteEntree: selectPropertyToText(
|
||||
pageProperties,
|
||||
propContexteEntree
|
||||
) as ContexteEntreeDC,
|
||||
Integration: datePropertyToDate(pageProperties, "Intégration"),
|
||||
Sortie: datePropertyToDate(pageProperties, "Sortie"),
|
||||
Evenements: familyEvents.filter((fe) => fe.notionIdFamille === page.id),
|
||||
// Ces 4 propriétés seront peuplés après le data consistency check
|
||||
EvenementsDates: [],
|
||||
EvenementsEL: [],
|
||||
EvenementsAvantEL: [],
|
||||
EvenementsApresEL: [],
|
||||
Departement: departement?.name || null,
|
||||
DerniereModification: datePropertyToDate(
|
||||
pageProperties,
|
||||
propDerniereModification
|
||||
)!,
|
||||
Penal: statusPropertyToText(pageProperties, propPenal) as StatutPenal,
|
||||
Social: statusPropertyToText(pageProperties, propSocial) as StatutSocial,
|
||||
Missions: contacts
|
||||
.filter((c) => c.notionIdFamille === page.id)
|
||||
.flatMap((c) => c.Missions),
|
||||
Contacts: contacts.filter((c) => c.notionIdFamille === page.id),
|
||||
};
|
||||
return family;
|
||||
}
|
||||
32
src/notion/fetch/fetchMissions.ts
Normal file
32
src/notion/fetch/fetchMissions.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { Client, isFullPage } from "@notionhq/client";
|
||||
import { Mission } from "../../data/Mission";
|
||||
import { relationPropertyToPageIds } from "../utils/properties/relationPropertyToPageIds";
|
||||
import { selectPropertyToText } from "../utils/properties/selectPropertyToText";
|
||||
import { titlePropertyToText } from "../utils/properties/titlePropertyToText";
|
||||
import { queryAllDbResults } from "../utils/queryAllDbResults";
|
||||
import { missionsDbId } from "./dbIds";
|
||||
|
||||
export async function fetchMissions(
|
||||
notionClient: Client,
|
||||
cacheConfig: boolean | { ttl: number }
|
||||
) {
|
||||
const missionsPages = (
|
||||
await queryAllDbResults(
|
||||
notionClient,
|
||||
{
|
||||
database_id: missionsDbId,
|
||||
},
|
||||
{ cache: cacheConfig }
|
||||
)
|
||||
).filter(isFullPage);
|
||||
const missions: Mission[] = missionsPages.map((page) => ({
|
||||
notionId: page.id,
|
||||
Nom: titlePropertyToText(page.properties, "Nom"),
|
||||
Equipe: selectPropertyToText(page.properties, "Équipe")!,
|
||||
ContactsNotionIds: relationPropertyToPageIds(
|
||||
page.properties,
|
||||
"📔 Contacts"
|
||||
),
|
||||
}));
|
||||
return missions;
|
||||
}
|
||||
119
src/notion/publish/publishStatsDepartementales.ts
Normal file
119
src/notion/publish/publishStatsDepartementales.ts
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import { Client } from "@notionhq/client";
|
||||
import {
|
||||
StatDepartement,
|
||||
StatDepartementales,
|
||||
} from "../../statistiques/v2/departemental/computeStatDepartement";
|
||||
import {
|
||||
CreatePageResponse,
|
||||
QueryDatabaseResponse,
|
||||
UpdatePageParameters,
|
||||
} from "@notionhq/client/build/src/api-endpoints";
|
||||
|
||||
export async function publishStatsDepartementales(
|
||||
notionClient: Client,
|
||||
deptStatsDbId: string,
|
||||
statsDepartementales: StatDepartementales
|
||||
): Promise<void> {
|
||||
console.log("Publication des statistiques départementales...");
|
||||
Object.entries(statsDepartementales).forEach(
|
||||
async ([deptName, stat]: [string, StatDepartement]) => {
|
||||
const deptStatPageId: string =
|
||||
(await findDeptStatPageId(notionClient, deptStatsDbId, deptName)) ||
|
||||
(await createEmptyDeptStatPageId(
|
||||
notionClient,
|
||||
deptStatsDbId,
|
||||
deptName
|
||||
));
|
||||
await updateDeptStatPage(notionClient, deptStatPageId, stat);
|
||||
}
|
||||
);
|
||||
}
|
||||
async function updateDeptStatPage(
|
||||
notionClient: Client,
|
||||
|
||||
deptStatPageId: string,
|
||||
stat: StatDepartement
|
||||
): Promise<void> {
|
||||
const props = {
|
||||
"Nb Familles": {
|
||||
number: stat.nbFamilles,
|
||||
},
|
||||
"Pénale ?": {
|
||||
checkbox: stat.hasProcedurePenale,
|
||||
},
|
||||
"% MED": {
|
||||
number: stat.pourcentageMED,
|
||||
},
|
||||
"% Pénale hors Gendarmerie": {
|
||||
number: stat.pourcentageProcedurePenaleHorsGendarmerie,
|
||||
},
|
||||
"% TC": {
|
||||
number: stat.pourcentageTC,
|
||||
},
|
||||
"% TP": {
|
||||
number: stat.pourcentageTP,
|
||||
},
|
||||
"Civile ?": {
|
||||
checkbox: stat.hasProcedureCivile,
|
||||
},
|
||||
"% IP": {
|
||||
number: stat.pourcentageIP,
|
||||
},
|
||||
"% MJIE ou AEMO": {
|
||||
number: stat.pourcentageMJIEouAEMO,
|
||||
},
|
||||
} as UpdatePageParameters["properties"];
|
||||
await notionClient.pages.update({
|
||||
page_id: deptStatPageId,
|
||||
properties: props,
|
||||
});
|
||||
}
|
||||
|
||||
async function findDeptStatPageId(
|
||||
notionClient: Client,
|
||||
deptStatsDbId: string,
|
||||
deptName: string
|
||||
): Promise<string | undefined> {
|
||||
const res: QueryDatabaseResponse = await notionClient.databases.query({
|
||||
database_id: deptStatsDbId,
|
||||
filter: {
|
||||
property: "Département",
|
||||
rich_text: {
|
||||
equals: deptName,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (res.results.length === 0) {
|
||||
return undefined;
|
||||
} else if (res.results.length === 1) {
|
||||
return res.results[0].id;
|
||||
} else {
|
||||
throw new Error(
|
||||
`Plusieurs pages trouvées pour le département ${deptName} dans la base de données des statistiques départementales.`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function createEmptyDeptStatPageId(
|
||||
notionClient: Client,
|
||||
deptStatsDbId: string,
|
||||
deptName: string
|
||||
): Promise<string> {
|
||||
const res: CreatePageResponse = await notionClient.pages.create({
|
||||
parent: {
|
||||
database_id: deptStatsDbId,
|
||||
},
|
||||
properties: {
|
||||
Département: {
|
||||
title: [
|
||||
{
|
||||
text: {
|
||||
content: deptName,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
return res.id;
|
||||
}
|
||||
106
src/statistiques/v2/departemental/computeStatDepartement.ts
Normal file
106
src/statistiques/v2/departemental/computeStatDepartement.ts
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { groupBy } from "lodash";
|
||||
import { Departement } from "../../../data/Departement";
|
||||
import {
|
||||
isInformationPreoccupante,
|
||||
isProcedureCivile,
|
||||
} from "../../../data/EvenementFamille";
|
||||
import { Famille } from "../../../data/Famille";
|
||||
import {
|
||||
isEvtTypeProcedurePenale,
|
||||
isEvtTypeProcedurePenaleHorsGendarmerie,
|
||||
} from "../../../data/TypeEvenementsPenal";
|
||||
import { filterFamillesWithOneOfEvenements } from "../filterFamillesWithOneOfEvenements";
|
||||
import { filterFamillesWithOneOfEvenementsOfType } from "../filterFamillesWithOneOfEvenementsOfType";
|
||||
import { percent } from "../math/percent";
|
||||
|
||||
export type StatDepartementales = {
|
||||
[departement: string]: StatDepartement;
|
||||
};
|
||||
|
||||
export function computeStatsDepartementales(
|
||||
familles: Famille[],
|
||||
departements: Departement[]
|
||||
): StatDepartementales {
|
||||
const famillesByDepartement: Record<string, Famille[]> = groupBy(
|
||||
familles,
|
||||
(f) => f.Departement
|
||||
);
|
||||
return Object.fromEntries(
|
||||
departements.map((d) => [
|
||||
d.name,
|
||||
computeStatsDepartement(famillesByDepartement[d.name] || []),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
export type StatDepartement = {
|
||||
nbFamilles: number;
|
||||
hasProcedurePenale: boolean;
|
||||
pourcentageMED: number | undefined;
|
||||
pourcentageProcedurePenaleHorsGendarmerie: number | undefined;
|
||||
pourcentageTC: number | undefined;
|
||||
pourcentageTP: number | undefined;
|
||||
hasProcedureCivile: boolean;
|
||||
pourcentageIP: number | undefined;
|
||||
pourcentageMJIEouAEMO: number | undefined;
|
||||
};
|
||||
|
||||
function computeStatsDepartement(
|
||||
famillesDepartements: Famille[]
|
||||
): StatDepartement {
|
||||
return {
|
||||
nbFamilles: famillesDepartements.length,
|
||||
hasProcedurePenale:
|
||||
filterFamillesWithOneOfEvenements(famillesDepartements, (e) =>
|
||||
isEvtTypeProcedurePenale(e.Type)
|
||||
).length > 0,
|
||||
pourcentageMED: percent(
|
||||
filterFamillesWithOneOfEvenementsOfType(
|
||||
famillesDepartements,
|
||||
"Mise en demeure de scolarisation"
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
pourcentageProcedurePenaleHorsGendarmerie: percent(
|
||||
filterFamillesWithOneOfEvenements(famillesDepartements, (e) =>
|
||||
isEvtTypeProcedurePenaleHorsGendarmerie(e.Type)
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
pourcentageTC: percent(
|
||||
filterFamillesWithOneOfEvenements(
|
||||
famillesDepartements,
|
||||
(e) => e.Type === "Tribunal de police judiciaire"
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
pourcentageTP: percent(
|
||||
filterFamillesWithOneOfEvenements(
|
||||
famillesDepartements,
|
||||
(e) => e.Type === "Tribunal de police judiciaire"
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
hasProcedureCivile:
|
||||
filterFamillesWithOneOfEvenements(famillesDepartements, (e) =>
|
||||
isProcedureCivile(e)
|
||||
).length > 0,
|
||||
pourcentageIP: percent(
|
||||
filterFamillesWithOneOfEvenements(famillesDepartements, (e) =>
|
||||
isInformationPreoccupante(e)
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
pourcentageMJIEouAEMO: percent(
|
||||
famillesDepartements.filter(
|
||||
(f) =>
|
||||
f.Social === "MJIE" ||
|
||||
f.Social === "AEMO" ||
|
||||
f.EvenementsEL.some(
|
||||
(e) => e.Type === "Classement suite AEMO MIJE ou..."
|
||||
)
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
};
|
||||
}
|
||||
42
src/statistiques/v2/penales/computeStatDepartement.ts
Normal file
42
src/statistiques/v2/penales/computeStatDepartement.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import { Famille } from "../../../data/Famille";
|
||||
import { isEvtTypeProcedurePenaleHorsGendarmerie } from "../../../data/TypeEvenementsPenal";
|
||||
import { filterFamillesWithOneOfEvenements } from "../filterFamillesWithOneOfEvenements";
|
||||
import { filterFamillesWithOneOfEvenementsOfType } from "../filterFamillesWithOneOfEvenementsOfType";
|
||||
import { percent } from "../math/percent";
|
||||
import { nbFamillesAvecPagesLiees } from "../nbFamillesAvecPagesLiees";
|
||||
import { StatsPenales } from "./StatsPenales";
|
||||
|
||||
export function computeStatDepartement(
|
||||
famillesDepartements: Famille[]
|
||||
): StatsPenales["parDepartements"][string] {
|
||||
return {
|
||||
nbFamilles: nbFamillesAvecPagesLiees(famillesDepartements),
|
||||
pourcentageMED: percent(
|
||||
filterFamillesWithOneOfEvenementsOfType(
|
||||
famillesDepartements,
|
||||
"Mise en demeure de scolarisation"
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
pourcentageProcedurePenaleHorsGendarmerie: percent(
|
||||
filterFamillesWithOneOfEvenements(famillesDepartements, (e) =>
|
||||
isEvtTypeProcedurePenaleHorsGendarmerie(e.Type)
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
pourcentageTribunalCorrectionnel: percent(
|
||||
filterFamillesWithOneOfEvenements(
|
||||
famillesDepartements,
|
||||
(e) => e.Type === "Tribunal correctionnel"
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
pourcentageTribunalPolice: percent(
|
||||
filterFamillesWithOneOfEvenements(
|
||||
famillesDepartements,
|
||||
(e) => e.Type === "Tribunal de police judiciaire"
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
@ -23,9 +23,8 @@ import {
|
|||
} from "./computeFamilleAvecInfosProceduresPenales";
|
||||
import { computeIntervalMedGendarmerieOuProcureur } from "./intervals/computeIntervalMedGendarmerieOuProcureur";
|
||||
import { groupBy } from "lodash";
|
||||
import { percent } from "../math/percent";
|
||||
import { isEvtTypeProcedurePenaleHorsGendarmerie } from "../../../data/TypeEvenementsPenal";
|
||||
import { computeStatsAmendes } from "./computeStatsAmendes";
|
||||
import { computeStatDepartement } from "./computeStatDepartement";
|
||||
|
||||
export type FamilleAvecInfoTribunaux = Famille & {
|
||||
infoTribunaux: InfoTribunalCorrectionnel[];
|
||||
|
|
@ -180,41 +179,6 @@ export function computeStatsPenales(familles: Famille[]): StatsPenales {
|
|||
return statsPenales;
|
||||
}
|
||||
|
||||
function computeStatDepartement(
|
||||
famillesDepartements: Famille[]
|
||||
): StatsPenales["parDepartements"][string] {
|
||||
return {
|
||||
nbFamilles: nbFamillesAvecPagesLiees(famillesDepartements),
|
||||
pourcentageMED: percent(
|
||||
filterFamillesWithOneOfEvenementsOfType(
|
||||
famillesDepartements,
|
||||
"Mise en demeure de scolarisation"
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
pourcentageProcedurePenaleHorsGendarmerie: percent(
|
||||
filterFamillesWithOneOfEvenements(famillesDepartements, (e) =>
|
||||
isEvtTypeProcedurePenaleHorsGendarmerie(e.Type)
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
pourcentageTribunalCorrectionnel: percent(
|
||||
filterFamillesWithOneOfEvenements(
|
||||
famillesDepartements,
|
||||
(e) => e.Type === "Tribunal correctionnel"
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
pourcentageTribunalPolice: percent(
|
||||
filterFamillesWithOneOfEvenements(
|
||||
famillesDepartements,
|
||||
(e) => e.Type === "Tribunal de police judiciaire"
|
||||
).length,
|
||||
famillesDepartements.length
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function computeCrpc(
|
||||
famillesResistantesOuEx: Famille[]
|
||||
): StatsPenales["procureur"]["crpc"] {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue