feat: improve consistency check and dry-run
parent
2a26eb9dd2
commit
f3764a98f5
|
@ -15,4 +15,6 @@
|
||||||
.env
|
.env
|
||||||
dist
|
dist
|
||||||
test-coverage
|
test-coverage
|
||||||
node_modules
|
node_modules
|
||||||
|
el-stats-par-anciennete.json
|
||||||
|
el-stats.json
|
||||||
|
|
|
@ -7,7 +7,7 @@ before_script:
|
||||||
update-statistics:
|
update-statistics:
|
||||||
script:
|
script:
|
||||||
- echo "NOTION_TOKEN=$NOTION_TOKEN" > .env
|
- echo "NOTION_TOKEN=$NOTION_TOKEN" > .env
|
||||||
- yarn run start
|
- yarn run publish-stats
|
||||||
rules:
|
rules:
|
||||||
# This ensures that only pushes to the default branch will trigger
|
# This ensures that only pushes to the default branch will trigger
|
||||||
# a pages deploy
|
# a pages deploy
|
||||||
|
|
|
@ -5,4 +5,4 @@ This modules computes EL statistiques from Notion Data
|
||||||
# Setup
|
# Setup
|
||||||
|
|
||||||
- Create file `.env` defining the NOTION_TOKEN variable
|
- Create file `.env` defining the NOTION_TOKEN variable
|
||||||
- Run `yarn install && yarn run start`
|
- Run `yarn dry-run` or `yarn publish-stats`
|
||||||
|
|
|
@ -23,7 +23,8 @@
|
||||||
"typescript-eslint": "^7.11.0"
|
"typescript-eslint": "^7.11.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node -r ts-node/register --env-file=.env src/index.ts",
|
"publish-stats": "node -r ts-node/register --env-file=.env src/index.ts",
|
||||||
|
"dry-run": "node -r ts-node/register --env-file=.env src/index.ts --dry-run",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,7 @@ const categorieEvenement: {
|
||||||
["Tribunal de police judiciaire"]: "Procédure Pénale",
|
["Tribunal de police judiciaire"]: "Procédure Pénale",
|
||||||
["Mise en demeure de scolarisation"]: "Procédure Pénale",
|
["Mise en demeure de scolarisation"]: "Procédure Pénale",
|
||||||
["Audition gendarmerie / police"]: "Procédure Pénale",
|
["Audition gendarmerie / police"]: "Procédure Pénale",
|
||||||
|
["Gendarmerie/Forces de l'ordre"]: "Procédure Pénale",
|
||||||
["Passage police municipale"]: "Procédure Pénale",
|
["Passage police municipale"]: "Procédure Pénale",
|
||||||
["Convocation procureur"]: "Procédure Pénale",
|
["Convocation procureur"]: "Procédure Pénale",
|
||||||
["Audition procureur"]: "Procédure Pénale",
|
["Audition procureur"]: "Procédure Pénale",
|
||||||
|
@ -71,6 +72,8 @@ const categorieEvenement: {
|
||||||
["Refus de contrôle"]: "Autre",
|
["Refus de contrôle"]: "Autre",
|
||||||
["Administrateur AD'HOC"]: "Autre",
|
["Administrateur AD'HOC"]: "Autre",
|
||||||
["Signalement"]: "Autre",
|
["Signalement"]: "Autre",
|
||||||
|
["contrôle URSSAF"]: "Autre",
|
||||||
|
["contrôle fiscal"]: "Autre",
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CategorieEvenement =
|
export type CategorieEvenement =
|
||||||
|
|
|
@ -28,4 +28,7 @@ export type TypeEvenement =
|
||||||
| "Rappel à la loi"
|
| "Rappel à la loi"
|
||||||
| "Passage police municipale"
|
| "Passage police municipale"
|
||||||
| "Administrateur AD'HOC"
|
| "Administrateur AD'HOC"
|
||||||
| "Validation désobéissance";
|
| "Validation désobéissance"
|
||||||
|
| "contrôle URSSAF"
|
||||||
|
| "contrôle fiscal"
|
||||||
|
| "Gendarmerie/Forces de l'ordre";
|
||||||
|
|
|
@ -1,54 +1,82 @@
|
||||||
import { isValidEvenementFamille } from "./EvenementFamille";
|
import { isValidEvenementFamille } from "./EvenementFamille";
|
||||||
import { Famille } from "./Famille";
|
import { Famille, isExResistant, isResistant } from "./Famille";
|
||||||
|
|
||||||
export function checkDataConsistency(families: Famille[]): ConsistencyIssue[] {
|
export function checkDataConsistency(families: Famille[]): ConsistencyReport {
|
||||||
return families.flatMap((family) => {
|
const reports = families.map((family) => {
|
||||||
return checkFamilyDataConsistency(family);
|
return checkFamilyDataConsistency(family);
|
||||||
});
|
});
|
||||||
|
return {
|
||||||
|
errors: reports.flatMap((r) => r.errors),
|
||||||
|
warnings: reports.flatMap((r) => r.warnings),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ConsistencyReport = {
|
||||||
|
warnings: ConsistencyIssue[];
|
||||||
|
errors: ConsistencyIssue[];
|
||||||
|
};
|
||||||
export type ConsistencyIssue = {
|
export type ConsistencyIssue = {
|
||||||
issueType: string;
|
issueType: string;
|
||||||
familyId: string;
|
familyId: string;
|
||||||
canIgnore?: boolean;
|
|
||||||
};
|
};
|
||||||
function checkFamilyDataConsistency(family: Famille) {
|
function checkFamilyDataConsistency(family: Famille): ConsistencyReport {
|
||||||
const consistencyIssues: ConsistencyIssue[] = [];
|
const consistencyErrors: ConsistencyIssue[] = [];
|
||||||
|
const consistencyWarnings: ConsistencyIssue[] = [];
|
||||||
|
|
||||||
if (family.Statut === "Résistant.e") {
|
if (family.Statut === "Résistant.e") {
|
||||||
if (family.Integration === null) {
|
if (family.Integration === null) {
|
||||||
consistencyIssues.push({
|
consistencyErrors.push({
|
||||||
familyId: family.Titre,
|
familyId: family.Titre,
|
||||||
issueType: "Résistant.e without startResistant",
|
issueType: "Résistant.e without startResistant",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (family.Sortie !== null) {
|
if (family.Sortie !== null) {
|
||||||
consistencyIssues.push({
|
consistencyErrors.push({
|
||||||
familyId: family.Titre,
|
familyId: family.Titre,
|
||||||
issueType: "Résistant.e with endResistant!!",
|
issueType: "Résistant.e with endResistant!!",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (family.Statut === "Ex résistant·e·s") {
|
} else if (family.Statut === "Ex résistant·e·s") {
|
||||||
if (family.Integration === null) {
|
if (family.Integration === null) {
|
||||||
consistencyIssues.push({
|
consistencyErrors.push({
|
||||||
familyId: family.Titre,
|
familyId: family.Titre,
|
||||||
issueType: "Ex résistant.e.s without startResistant",
|
issueType: "Ex résistant.e.s without startResistant",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (family.Sortie === null) {
|
if (family.Sortie === null) {
|
||||||
consistencyIssues.push({
|
consistencyErrors.push({
|
||||||
familyId: family.Titre,
|
familyId: family.Titre,
|
||||||
issueType: "Ex résistant.e.s without endResistant",
|
issueType: "Ex résistant.e.s without endResistant",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (family.Integration! > family.Sortie!) {
|
if (family.Integration! > family.Sortie!) {
|
||||||
consistencyIssues.push({
|
consistencyErrors.push({
|
||||||
familyId: family.Titre,
|
familyId: family.Titre,
|
||||||
issueType: "startResistsant > endResistant ",
|
issueType: "startResistsant > endResistant ",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
consistencyIssues.push(
|
if (
|
||||||
|
(isResistant(family) || isExResistant(family)) &&
|
||||||
|
family.Integration !== null
|
||||||
|
) {
|
||||||
|
const miseEnDemeureBeforeInteg =
|
||||||
|
family.Evenements.find(
|
||||||
|
(e) =>
|
||||||
|
e.Type === "Mise en demeure de scolarisation" &&
|
||||||
|
(e.Date === null || e.Date < family.Integration!)
|
||||||
|
) !== undefined;
|
||||||
|
if (
|
||||||
|
miseEnDemeureBeforeInteg &&
|
||||||
|
family.ContexteEntree !== "Après mise en demeure"
|
||||||
|
) {
|
||||||
|
consistencyWarnings.push({
|
||||||
|
familyId: family.Titre,
|
||||||
|
issueType: `ContextEntree incorrect`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
consistencyErrors.push(
|
||||||
...family.Evenements.filter((e) => !isValidEvenementFamille(e.Type)).map(
|
...family.Evenements.filter((e) => !isValidEvenementFamille(e.Type)).map(
|
||||||
(e) => ({
|
(e) => ({
|
||||||
familyId: family.Titre,
|
familyId: family.Titre,
|
||||||
|
@ -56,14 +84,16 @@ function checkFamilyDataConsistency(family: Famille) {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
consistencyIssues.push(
|
consistencyWarnings.push(
|
||||||
...family.Evenements.filter((e) => e.Type !== null && e.Date === null).map(
|
...family.Evenements.filter((e) => e.Type !== null && e.Date === null).map(
|
||||||
(e) => ({
|
(e) => ({
|
||||||
familyId: family.Titre,
|
familyId: family.Titre,
|
||||||
issueType: `Event ${e.notionId} with non null Type "${e.Type}" but null Date`,
|
issueType: `Event ${e.notionId} with non null Type "${e.Type}" but null Date`,
|
||||||
canIgnore: true,
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
return consistencyIssues;
|
return {
|
||||||
|
errors: consistencyErrors,
|
||||||
|
warnings: consistencyWarnings,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
57
src/index.ts
57
src/index.ts
|
@ -1,40 +1,71 @@
|
||||||
import { Client } from "@notionhq/client";
|
import { Client } from "@notionhq/client";
|
||||||
|
import { writeFileSync } from "fs";
|
||||||
import { checkDataConsistency } from "./data/checkDataConsistency";
|
import { checkDataConsistency } from "./data/checkDataConsistency";
|
||||||
import { fetchFamiliesWithEventsFromNotion } from "./notion/fetch/fetchFamiliesWithEventsFromNotion";
|
import { fetchFamiliesWithEventsFromNotion } from "./notion/fetch/fetchFamiliesWithEventsFromNotion";
|
||||||
import { publishStatisticsToNotion } from "./notion/publish/publishStatisticsToNotion";
|
import { publishStatisticsToNotion } from "./notion/publish/publishStatisticsToNotion";
|
||||||
import { computeELStats } from "./statistiques/computeELStats";
|
import { computeELStats } from "./statistiques/computeELStats";
|
||||||
|
|
||||||
(async function IIFE() {
|
type ProcessOptions = {
|
||||||
|
dryRun: boolean;
|
||||||
|
notionApiToken: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build options from process args and env
|
||||||
|
*/
|
||||||
|
function buildProcessOptions(): ProcessOptions {
|
||||||
const notionApiToken = process.env.NOTION_TOKEN;
|
const notionApiToken = process.env.NOTION_TOKEN;
|
||||||
if (!notionApiToken) {
|
if (!notionApiToken) {
|
||||||
throw new Error("process.env.NOTION_TOKEN not defined");
|
throw new Error("process.env.NOTION_TOKEN not defined");
|
||||||
}
|
}
|
||||||
|
const dryRun = process.argv.length >= 2 && process.argv[2] === "--dry-run";
|
||||||
|
return {
|
||||||
|
dryRun: dryRun,
|
||||||
|
notionApiToken: notionApiToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
(async function IIFE() {
|
||||||
|
const options = buildProcessOptions();
|
||||||
|
|
||||||
const notionClient = new Client({
|
const notionClient = new Client({
|
||||||
auth: notionApiToken,
|
auth: options.notionApiToken,
|
||||||
});
|
});
|
||||||
|
|
||||||
const doFetch = true;
|
const doFetch = true;
|
||||||
console.log("Fetching families...");
|
console.log("Fetching families...");
|
||||||
const families = doFetch
|
const familles = doFetch
|
||||||
? await fetchFamiliesWithEventsFromNotion(notionClient)
|
? await fetchFamiliesWithEventsFromNotion(notionClient)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
console.log("Checking Data Consistency issues...");
|
console.log("Checking Data Consistency issues...");
|
||||||
const consistencyIssues = checkDataConsistency(families);
|
const consistencyReport = checkDataConsistency(familles);
|
||||||
if (consistencyIssues.length > 0) {
|
if (consistencyReport.errors.length > 0) {
|
||||||
console.error("Found consistency issues:");
|
console.error(
|
||||||
console.error(consistencyIssues);
|
`Found ${consistencyReport.errors.length} consistency error(s) that prevent building stats:`
|
||||||
if (consistencyIssues.find((issue) => !issue.canIgnore)) {
|
);
|
||||||
process.exit(1);
|
console.error(consistencyReport.errors);
|
||||||
}
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (consistencyReport.warnings.length > 0) {
|
||||||
|
console.warn(
|
||||||
|
`Found ${consistencyReport.warnings.length} non blocking consistency warning(s):`
|
||||||
|
);
|
||||||
|
console.warn(consistencyReport.warnings);
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentDate = new Date(Date.now());
|
const currentDate = new Date(Date.now());
|
||||||
|
|
||||||
console.log("Building statistics...");
|
console.log("Building statistics...");
|
||||||
const resistantCountStats = computeELStats(families, currentDate);
|
const elStats = computeELStats(familles, currentDate);
|
||||||
|
|
||||||
console.log("Publishing statistics...");
|
if (options.dryRun) {
|
||||||
publishStatisticsToNotion(resistantCountStats, currentDate, notionClient);
|
console.log(
|
||||||
|
"Dry run => Skip Publishing. Stats are dumped in file el-stats.json"
|
||||||
|
);
|
||||||
|
writeFileSync("./el-stats.json", JSON.stringify(elStats, null, " "));
|
||||||
|
} else {
|
||||||
|
console.log("Publishing statistics...");
|
||||||
|
publishStatisticsToNotion(elStats, currentDate, notionClient);
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
Loading…
Reference in New Issue