Skip to content

Commit

Permalink
feat: ajout du filtre secteur professionnel (#3155)
Browse files Browse the repository at this point in the history
  • Loading branch information
totakoko authored Aug 31, 2023
1 parent a6ea915 commit b2f8b2c
Show file tree
Hide file tree
Showing 24 changed files with 612 additions and 68 deletions.
1 change: 1 addition & 0 deletions scripts/preventSensibleFilesCommit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ exception="$exception|open-api.json"
exception="$exception|server/src/jobs/hydrate/reference/bassins_emploi_communes.json"
exception="$exception|eslintrc.json|app.json|jsconfig.json|tsconfig.json|rome.json"
exception="$exception)$|^server/tests)"
exception="$exception|ui/public/arborescence-rome-14-06-2021.json"
exception="$exception|ui/public/modele-import.xlsx"

if grep -q vault ".infra/ansible/roles/setup/vars/main/vault.yml"; then
Expand Down
6 changes: 4 additions & 2 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@
"@supercharge/promise-pool": "2.4.0",
"@types/boom": "7.3.2",
"JSONStream": "^1.3.5",
"adm-zip": "^0.5.10",
"arg": "5.0.1",
"async": "3.2.3",
"axios": "0.27.2",
"axios-cache-interceptor": "^0.10.7",
"axios-retry": "3.3.1",
"body-parser": "1.20.0",
"boom": "7.3.0",
"bson": "5.4.0",
"bunyan": "1.8.15",
"bunyan-prettystream": "0.1.3",
"bson": "5.4.0",
"checksum-stream": "1.0.3",
"clamscan": "2.1.2",
"cli-progress": "3.11.0",
Expand All @@ -52,6 +53,7 @@
"express": "4.18.1",
"express-async-errors": "3.1.1",
"express-list-endpoints": "^6.0.0",
"fast-xml-parser": "^4.2.7",
"fs-extra": "10.1.0",
"iconv-lite": "0.6.3",
"joi": "17.6.0",
Expand Down Expand Up @@ -94,7 +96,7 @@
"tsx": "3.12.6",
"type-fest": "^3.10.0",
"uuid": "8.3.2",
"xlsx": "0.18.5",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.0/xlsx-0.20.0.tgz",
"zod": "3.21.4",
"zod-express-middleware": "1.4.0",
"zod-to-json-schema": "3.21.4"
Expand Down
105 changes: 105 additions & 0 deletions server/scripts/extract-arborescence-rome.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
Ce script construit l'arborescence des domaines et sous-domaines d'activité à partir du excel officiel téléchargeable
depuis la page https://www.pole-emploi.fr/employeur/vos-recrutements/le-rome-et-les-fiches-metiers.html
Étapes :
- récupération du fichier https://www.pole-emploi.fr/files/live/sites/PE/files/ROME_ArboPrincipale.xlsx
- transformation en un arbre d'objets
- écriture dans un fichier arborescence-rome.json
Puis prendre ce fichier et le mettre dans ui/public/arborescence-rome-14-06-2021.json
*/
import { writeFileSync } from "fs";

import axios from "axios";
import { read, utils } from "xlsx";

async function main() {
const res = await axios.get("https://www.pole-emploi.fr/files/live/sites/PE/files/ROME_ArboPrincipale.xlsx", {
responseType: "arraybuffer",
});
if (res.status !== 200) {
throw new Error(`Invalid response status. Expected 200 but received ${res.status}`);
}

const workbook = read(res.data);
const arboPrincipaleSheetName = workbook.SheetNames.find((name) => name.includes("Arbo Principale"));
if (!arboPrincipaleSheetName) {
throw new Error(`Onglet Arbo Principale non trouvée. Le fichier a sans doute changé de format.`);
}
console.log(`Données : ${arboPrincipaleSheetName}`);
const arboPrincipaleSheet = workbook.Sheets[arboPrincipaleSheetName];

const rawJsonData = utils
.sheet_to_json<Record<"familleMetier" | "domaineProfessionnel" | "numeroOrdre" | "name" | "codeOGR", any>>(
arboPrincipaleSheet,
{
// exemple : A 11 01 Chauffeur / Chauffeuse de machines agricoles 11987
header: ["familleMetier", "domaineProfessionnel", "numeroOrdre", "name", "codeOGR"],
}
)
.slice(1); // removes the header

const arborescenceRome = rawJsonData.reduce((acc, row) => {
// conditions ordonnées de la plus restrictive à la moins restrictive
if (row.codeOGR !== " ") {
// pas besoin des appellations de métiers
// acc[row.familleMetier].children[row.domaineProfessionnel].children[row.numeroOrdre].children[row.codeOGR] = {
// id: `${row.familleMetier}${row.domaineProfessionnel}${row.numeroOrdre}-${row.codeOGR}`,
// codeOGR: row.codeOGR,
// name: row.name,
// };
} else if (row.numeroOrdre !== " ") {
acc[row.familleMetier].children[row.domaineProfessionnel].children[row.numeroOrdre] = {
id: `${row.familleMetier}${row.domaineProfessionnel}${row.numeroOrdre}`,
name: row.name,
// children: {},
};
} else if (row.domaineProfessionnel !== " ") {
acc[row.familleMetier].children[row.domaineProfessionnel] = {
id: `${row.familleMetier}${row.domaineProfessionnel}`,
name: row.name,
children: {},
};
} else {
acc[row.familleMetier] = {
id: row.familleMetier,
name: row.name,
children: {},
};
}
return acc;
}, {});

const famillesMetiers = Object.keys(arborescenceRome)
.sort()
.map((key) => {
const famille = arborescenceRome[key];
return {
...famille,
children: Object.keys(famille.children)
.sort()
.map((key) => {
const domaine = famille.children[key];
return {
...domaine,
children: Object.keys(domaine.children)
.sort()
.map((key) => domaine.children[key]),
};
}),
};
});

famillesMetiers.forEach((familleMetier) => addIdsToNames(familleMetier));

writeFileSync("arborescence-rome.json", Buffer.from(JSON.stringify(famillesMetiers), "utf8"));
console.log("Fichier créé : arborescence-rome.json");
}

function addIdsToNames(node: any) {
node.name = `${node.id}${node.name}`;
node.children?.forEach((node) => addIdsToNames(node));
}

main();
18 changes: 18 additions & 0 deletions server/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,24 @@ program
}
});

program
.command("hydrate:rncp-romes")
.description("Remplissage du RNCP")
.option("-s, --sync", "Run job synchronously")
.action(async ({ sync }) => {
const exitCode = await addJob(
{
name: "hydrate:rncp-romes",
sync,
},
{ runningLogs: true }
);

if (exitCode) {
program.error("Command failed", { exitCode });
}
});

program
.command("hydrate:organismes-formations")
.description("Remplissage des formations des organismes")
Expand Down
5 changes: 5 additions & 0 deletions server/src/common/actions/helpers/filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export const fullEffectifsFiltersSchema = {
.optional(),
formation_niveaux: z.preprocess((str: any) => str.split(","), z.array(z.string())).optional(),
formation_cfds: z.preprocess((str: any) => str.split(","), z.array(z.string())).optional(),
formation_secteursProfessionnels: z.preprocess((str: any) => str.split(","), z.array(z.string())).optional(),
};

export type FullEffectifsFilters = z.infer<z.ZodObject<typeof fullEffectifsFiltersSchema>>;
Expand Down Expand Up @@ -245,6 +246,10 @@ export const fullEffectifsFiltersConfigurations: {
matchKey: "formation.cfd",
transformValue: (value) => ({ $in: value }),
},
formation_secteursProfessionnels: {
matchKey: "_computed.formation.codes_rome",
transformValue: (value) => ({ $in: value }),
},
};

export function buildMongoFilters<
Expand Down
3 changes: 3 additions & 0 deletions server/src/common/model/@types/Effectif.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1858,5 +1858,8 @@ export interface Effectif {
*/
fiable?: boolean;
};
formation?: {
codes_rome?: string[];
};
};
}
6 changes: 6 additions & 0 deletions server/src/common/model/@types/Rncp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// auto-generated by bson-schema-to-typescript

export interface Rncp {
rncp: string;
romes: string[];
}
4 changes: 4 additions & 0 deletions server/src/common/model/collections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { BassinsEmploi } from "./@types/BassinsEmploi";
import { EffectifsQueue } from "./@types/EffectifsQueue";
import { FormationsCatalogue } from "./@types/FormationsCatalogue";
import { OrganismeSoltea } from "./@types/OrganismeSoltea";
import { Rncp } from "./@types/Rncp";
import { UaisAcceReferentiel } from "./@types/UaisAcceReferentiel.js";
import bassinsEmploiDescriptor from "./bassinsEmploi.model";
import effectifsModelDescriptor from "./effectifs.model/effectifs.model";
Expand All @@ -33,6 +34,7 @@ import organisationsModelDescriptor, { Organisation } from "./organisations.mode
import OrganismesModelDescriptor from "./organismes.model";
import OrganismesReferentielModelDescriptor from "./organismesReferentiel.model";
import organismesSolteaModelDescriptor from "./organismesSoltea.model";
import rncpModelDescriptor from "./rncp.model";
import uaisAcceReferentielModelDescriptor from "./uaisAcceReferentiel.model";
import uploadsModelDescriptor from "./uploads.model/uploads.model";
import usersModelDescriptor from "./users.model";
Expand All @@ -56,6 +58,7 @@ export const modelDescriptors = [
uploadsModelDescriptor,
fiabilisationUaiSiretModelDescriptor,
bassinsEmploiDescriptor,
rncpModelDescriptor,
];

export const formationsDb = () => getDbCollection<Formation>(formationsModelDescriptor.collectionName);
Expand Down Expand Up @@ -83,3 +86,4 @@ export const fiabilisationUaiSiretDb = () =>
export const bassinsEmploiDb = () => getDbCollection<BassinsEmploi>(bassinsEmploiDescriptor.collectionName);
export const organismesSolteaDb = () =>
getDbCollection<OrganismeSoltea>(organismesSolteaModelDescriptor.collectionName);
export const rncpDb = () => getDbCollection<Rncp>(rncpModelDescriptor.collectionName);
4 changes: 4 additions & 0 deletions server/src/common/model/effectifs.model/effectifs.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ const indexes: [IndexSpecification, CreateIndexesOptions][] = [
[{ "_computed.organisme.uai": 1 }, {}],
[{ "_computed.organisme.siret": 1 }, {}],
[{ "_computed.organisme.fiable": 1 }, {}],
[{ "_computed.formation.codes_rome": 1 }, {}],
];

export const schema = object(
Expand Down Expand Up @@ -144,6 +145,9 @@ export const schema = object(
siret: string({ pattern: SIRET_REGEX_PATTERN, maxLength: 14, minLength: 14 }),
fiable: boolean({ description: `organismes.fiabilisation_statut == "FIABLE" && ferme != false` }),
}),
formation: object({
codes_rome: arrayOf(string()),
}),
},
{
description: "Propriétés calculées ou récupérées d'autres collections",
Expand Down
18 changes: 18 additions & 0 deletions server/src/common/model/rncp.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { CreateIndexesOptions, IndexSpecification } from "mongodb";

import { arrayOf, object, objectId, string } from "./json-schema/jsonSchemaTypes";

const collectionName = "rncp";

const indexes: [IndexSpecification, CreateIndexesOptions][] = [[{ rncp: 1 }, { unique: true }]];

const schema = object(
{
_id: objectId(),
rncp: string(),
romes: arrayOf(string()),
},
{ required: ["rncp", "romes"], additionalProperties: false }
);

export default { schema, indexes, collectionName };
12 changes: 12 additions & 0 deletions server/src/jobs/hydrate/effectifs/hydrate-effectifs-computed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ export async function hydrateEffectifsComputed() {
as: "_organisme",
},
},
{
$lookup: {
from: "rncp",
localField: "formation.rncp",
foreignField: "rncp",
as: "_rncp",
},
},
{
$addFields: {
_computed: {
Expand All @@ -28,12 +36,16 @@ export async function hydrateEffectifsComputed() {
bassinEmploi: { $first: "$_organisme.adresse.bassinEmploi" },
fiable: { $cond: [{ $eq: [{ $first: "$_organisme.fiabilisation_statut" }, "FIABLE"] }, true, false] },
},
formation: {
codes_rome: { $ifNull: [{ $first: "$_rncp.romes" }, []] },
},
},
},
},
{
$project: {
_organisme: 0,
_rncp: 0,
},
},
{
Expand Down
Loading

0 comments on commit b2f8b2c

Please sign in to comment.