Skip to content

Commit

Permalink
feat: test recherche
Browse files Browse the repository at this point in the history
  • Loading branch information
K4ST0R committed Nov 4, 2024
1 parent bdbd771 commit 68d7556
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 116 deletions.
2 changes: 0 additions & 2 deletions server/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import { importFormationSimilaire } from "./jobs/formations/importFormationSimil
import { importFichesFormationsTmp } from "./jobs/formations/importFichesFormationsTmp";
import { importIndicateurPoursuiteRegionale } from "./jobs/exposition/importIndicateurPoursuiteRegionale";
import { importFamillesMetiers } from "./jobs/formations/importFamillesMetiers";
import { createSearchIndex } from "./jobs/formations/createSearchIndex";

const cli = new Command();

Expand Down Expand Up @@ -67,7 +66,6 @@ const formationEtablissementJobs = [
{ name: "feIndicateurPoursuite", job: importIndicateurPoursuite },
{ name: "feFormationTag", job: computeFormationTag },
{ name: "feFormationSimilaire", job: importFormationSimilaire },
{ name: "feCreateSearchIndex", job: createSearchIndex },
];

const etablissementJobs = [
Expand Down
1 change: 0 additions & 1 deletion server/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ const config = {
formation: {
files: {
formationSimilaire: path.join(currentDir, "..", "data", "formationSimilaire.json"),
fuseIndex: path.join(currentDir, "..", "data", "fuseIndex.json"),
},
},
bcn: {
Expand Down
59 changes: 0 additions & 59 deletions server/src/jobs/formations/createSearchIndex.ts

This file was deleted.

99 changes: 45 additions & 54 deletions server/src/queries/getFormations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,11 @@ import { DB, Etablissement, Formation, FormationEtablissement } from "#src/commo
import { jsonBuildObject } from "kysely/helpers/postgres";
import FormationRepository from "#src/common/repositories/formation";
import config from "#src/config";
import { getSearch, getSearch2 } from "#src/services/search/search.js";

Check failure on line 13 in server/src/queries/getFormations.ts

View workflow job for this annotation

GitHub Actions / test

'getSearch' is defined but never used. Allowed unused vars must match /^_/u
import Fuse from "fuse.js";

Check failure on line 14 in server/src/queries/getFormations.ts

View workflow job for this annotation

GitHub Actions / test

'Fuse' is defined but never used. Allowed unused vars must match /^_/u
import fs from "fs";
import { createSearchIndex } from "#src/jobs/formations/createSearchIndex.js";

const logger = getLoggerWithContext("query");

// TODO : TO MOVE
const fuse = loadFuse();

async function loadFuse() {
await createSearchIndex();
if (!fs.existsSync(config.formation.files.fuseIndex)) {
return null;
}

const formationsIndex = JSON.parse(fs.readFileSync(config.formation.files.fuseIndex, "utf8") || null);
if (!formationsIndex) {
return null;
}

const fuse = new Fuse(
formationsIndex.formations,
{
...formationsIndex.options,
},
Fuse.parseIndex(formationsIndex.index)
);
return fuse;
}

async function getFuseInstance() {
return await fuse;
}

export function buildFilterTag(eb, tag) {
if (!tag) {
return eb;
Expand Down Expand Up @@ -254,29 +225,28 @@ async function buildFiltersFormationSQL({ cfds, domaine, formation }) {
queryFormation = queryFormation.where("cfd", "in", cfds);
}

if (formation) {
// Use fuse.js searching
const fuse = await getFuseInstance();
if (fuse) {
const searchResult = fuse.search<{ id: string }>(
`${formation
.split(" ")
.map((f) => `${f}`)
.join(" ")}`
);
queryFormation = queryFormation.where((eb) =>
eb("id", "=", eb.fn.any(sql.val(searchResult.map((r) => r.item.id))))
);
} else {
queryFormation = queryFormation.where((eb) =>
eb(
eb.fn("f_unaccent", ["libelle"]),
"ilike",
eb(sql.val("%"), "||", eb(eb.fn<string>("f_unaccent", [sql.val(formation)]), "||", "%"))
)
);
}
}
// if (formation) {
// const search = await getSearch();
// if (search) {
// const searchResult = search.search<{ id: string }>(
// `${formation
// .split(" ")
// .map((f) => `${f}`)
// .join(" ")}`
// );
// queryFormation = queryFormation.where((eb) =>
// eb("id", "=", eb.fn.any(sql.val(searchResult.map((r) => r.item.id))))
// );
// } else {
// queryFormation = queryFormation.where((eb) =>
// eb(
// eb.fn("f_unaccent", ["libelle"]),
// "ilike",
// eb(sql.val("%"), "||", eb(eb.fn<string>("f_unaccent", [sql.val(formation)]), "||", "%"))
// )
// );
// }
// }

if (!domaine) {
return { query: kdb.selectFrom(queryFormation.as("formation")).selectAll() };
Expand All @@ -299,7 +269,12 @@ async function buildFiltersFormationSQL({ cfds, domaine, formation }) {
}

export async function getFormationsSQL(
{ filtersEtablissement = {}, filtersFormation = {}, tag = null, millesime },
{
filtersEtablissement = {},
filtersFormation = { cfds: null, domaine: null, formation: null },
tag = null,
millesime,
},
pagination = { page: 1, limit: 100 }
) {
const page = pagination.page || 1;
Expand All @@ -311,6 +286,19 @@ export async function getFormationsSQL(

const queryFormation = await buildFiltersFormationSQL(filtersFormation as any);

// Fuse search
let filtersId: string[] = null;
if (filtersFormation.formation) {
const search = await getSearch2();
const searchResult = search.search<{ id: string }>(
`${filtersFormation.formation
.split(" ")
.map((f) => `${f}`)
.join(" ")}`
);
filtersId = searchResult.map((r) => r.item.id);
}

const results = await kdb
.selectFrom(
kdb
Expand All @@ -322,6 +310,9 @@ export async function getFormationsSQL(
.select((eb) => eb.fn("row_to_json", [sql`etablissement`]).as("etablissement"))
.select("formationEtablissement.id as id")
.$if(!!tag, (eb) => buildFilterTag(eb, tag))
.$if(!!filtersId, (eb) =>
eb.where((eb) => eb("formationEtablissement.id", "=", eb.fn.any(sql.val(filtersId))))
)
.where("formationEtablissement.millesime", "&&", [millesime])
.innerJoin(
queryEtablissement.query.as("etablissement"),
Expand Down
131 changes: 131 additions & 0 deletions server/src/services/search/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { oleoduc, writeData, transformData } from "oleoduc";
import { getLoggerWithContext } from "#src/common/logger.js";
import FormationRepository from "#src/common/repositories/formation";
import FormationEtablissementRepository from "#src/common/repositories/formationEtablissement";
import Fuse from "fuse.js";
import { uniq } from "lodash-es";
import fs from "fs";

Check failure on line 7 in server/src/services/search/search.ts

View workflow job for this annotation

GitHub Actions / test

'fs' is defined but never used. Allowed unused vars must match /^_/u
const logger = getLoggerWithContext("search");

let fuse: Fuse<any> = null;
let fuse2: Fuse<any> = null;

export async function createSearchIndex() {
logger.info(`Création d'un index de recherche pour les formations`);
const formations = [];
await oleoduc(
await FormationRepository.find({}),
transformData(async (data) => {
const formationsFamilleMetier = data.familleMetierId
? await FormationRepository.familleMetier(data.familleMetierId)
: [];
return { formation: data, formationsFamilleMetier };
}),
writeData(async ({ formation, formationsFamilleMetier }) => {
formations.push({
id: formation.id,
libelle: formation.libelle,
libelles: uniq([formation.libelle, ...formationsFamilleMetier.map((f) => f.libelle)]),
});
})
);

logger.info(`Indexation de ${formations.length} formations`);

const fuseOptions = {
ignoreLocation: true,
findAllMatches: true,
distance: 200,
threshold: 0.29,
useExtendedSearch: true,
includeScore: true,
keys: ["libelles"],
};
const myIndex = Fuse.createIndex(fuseOptions.keys, formations);
return {
formations: formations,
index: myIndex,
options: fuseOptions,
};
}

export async function createSearchIndex2() {
logger.info(`Création d'un index de recherche pour les formations dans un établissement`);
const formations = [];
await oleoduc(
await FormationEtablissementRepository.find({}),
transformData(async (data) => {
const formation = await FormationRepository.first({ id: data.formationId });
const formationsFamilleMetier = formation.familleMetierId
? await FormationRepository.familleMetier(formation.familleMetierId)
: [];
return { formationEtablissement: data, formation, formationsFamilleMetier };
}),
writeData(async ({ formationEtablissement, formation, formationsFamilleMetier }) => {
const filteredFamilleMetier = (
await Promise.all(
formationsFamilleMetier.map(async (familleMetier) => {
const exist = await FormationEtablissementRepository.first({
etablissementId: formationEtablissement.etablissementId,
formationId: familleMetier.id,
});
return { ...familleMetier, exist };
})
)
).filter((f) => f.exist);

formations.push({
id: formationEtablissement.id,
libelle: formation.libelle,
libelles: uniq([formation.libelle, ...filteredFamilleMetier.map((f) => f.libelle)]),
});
})
);

logger.info(`Indexation de ${formations.length} formations`);

const fuseOptions = {
ignoreLocation: true,
findAllMatches: true,
distance: 200,
threshold: 0.29,
useExtendedSearch: true,
includeScore: true,
keys: ["libelles"],
};
const myIndex = Fuse.createIndex(fuseOptions.keys, formations);

return {
formations: formations,
index: myIndex,
options: fuseOptions,
};
}

export async function getSearch(): Promise<Fuse<any>> {
if (!fuse) {
const { formations, index, options } = await createSearchIndex();
fuse = new Fuse(
formations,
{
...options,
},
Fuse.parseIndex(index)
);
}
return fuse;
}

export async function getSearch2(): Promise<Fuse<any>> {
if (!fuse2) {
const { formations, index, options } = await createSearchIndex2();
fuse2 = new Fuse(
formations,
{
...options,
},
Fuse.parseIndex(index)
);
}
return fuse2;
}

0 comments on commit 68d7556

Please sign in to comment.