@tekkare/romulus 0.1.53 → 0.1.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": ">=3.16.0"
6
6
  },
7
- "version": "0.1.52",
7
+ "version": "0.1.54",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Les options d'un filtre a referentiel long, cherchees dans l'index.
3
+ *
4
+ * Un filtre agrege un champ (les marques, les molecules) et cherche sur
5
+ * d'autres. La regle qui compte est la : sur un index ou une ligne porte a la
6
+ * fois la marque, la molecule, la classe et le laboratoire, chercher dans un
7
+ * filtre doit interroger AUSSI les champs freres, sans changer ce que le
8
+ * filtre remonte. « keytruda » tape dans le filtre molecule trouve la ligne
9
+ * par sa marque et l'agregation en sort PEMBROLIZUMAB ; « pembrolizumab »
10
+ * tape dans le filtre marque en sort Keytruda. Sans cela l'utilisateur doit
11
+ * connaitre le nom qu'il cherche dans le vocabulaire exact du filtre, ce qui
12
+ * est justement ce qu'il vient demander.
13
+ *
14
+ * Le module ne parle pas au moteur : il ecrit le corps de la requete et relit
15
+ * les seaux. Le transport (proxy, cache, index) reste a l'application.
16
+ */
17
+ export interface RmFilterOption {
18
+ label: string;
19
+ value: string;
20
+ }
21
+ export interface RmFilterOptionsSpec {
22
+ /**
23
+ * Ce que le filtre agrege. Un champ : la valeur est son propre libelle
24
+ * (`brand.keyword`). Deux champs `[libelle, valeur]` : le premier s'affiche,
25
+ * le second est ce que l'analyse filtre (un libelle ATC et son code).
26
+ */
27
+ field: string | [string, string];
28
+ /**
29
+ * Champs interroges par la saisie, dans l'ordre : le champ propre au filtre
30
+ * d'abord, ses synonymes ensuite. Champs analyses (`brand`), pas leur
31
+ * `.keyword` : un `.keyword` ne repond pas a une recherche partielle.
32
+ */
33
+ search: string[];
34
+ /** La saisie, telle qu'elle est tapee. Vide, tout le referentiel remonte. */
35
+ input: string;
36
+ /**
37
+ * Plafond de la liste. Au-dela, c'est la recherche qui trie : une liste
38
+ * coupee a cent laisse croire que ce qui suit n'existe pas.
39
+ */
40
+ size?: number;
41
+ }
42
+ /** Le corps de la requete d'agregation, a poster tel quel sur l'index. */
43
+ export declare function rmFilterOptionsQuery(spec: RmFilterOptionsSpec): {
44
+ size: number;
45
+ query: {
46
+ bool: {
47
+ must: {
48
+ query_string: {
49
+ fields: string[];
50
+ query: string;
51
+ analyze_wildcard: boolean;
52
+ default_operator: string;
53
+ };
54
+ }[];
55
+ };
56
+ };
57
+ aggs: {
58
+ count: {
59
+ multi_terms: {
60
+ terms: {
61
+ field: string;
62
+ }[];
63
+ size: number;
64
+ order: {
65
+ _key: string;
66
+ };
67
+ };
68
+ terms?: undefined;
69
+ } | {
70
+ terms: {
71
+ field: string;
72
+ size: number;
73
+ order: {
74
+ _key: string;
75
+ };
76
+ };
77
+ multi_terms?: undefined;
78
+ };
79
+ };
80
+ };
81
+ /** Les options, relues dans la reponse du moteur. */
82
+ export declare function rmFilterOptions(response: any): RmFilterOption[];
83
+ /** Un champ interroge par la saisie d'un filtre. */
84
+ export interface RmFilterSearchField {
85
+ /** Champ interroge. La recherche porte sur son `.keyword`. */
86
+ field: string;
87
+ /**
88
+ * Chemins nested a traverser pour l'atteindre, du plus large au plus
89
+ * etroit (`['brand_history']`, ou `['a', 'a.b']` pour deux niveaux).
90
+ * Absent, le champ est a la racine du document.
91
+ */
92
+ nested?: string[];
93
+ }
94
+ /**
95
+ * La clause qui restreint les documents a la saisie, sur plusieurs champs a la
96
+ * fois. Meme regle que `rmFilterOptionsQuery`, pour un index ou le champ frere
97
+ * ne vit pas au meme niveau que le champ agrege : le filtre marque d'un index
98
+ * d'avis agrege `brand_group` a la racine et cherche la molecule dans
99
+ * `brand_history`, si bien que « pembro » sort KEYTRUDA. Un champ nested
100
+ * interroge sans son enveloppe `nested` ne repond jamais : c'est un filtre qui
101
+ * parait vide plutot qu'un filtre en erreur, d'ou cette enveloppe explicite.
102
+ *
103
+ * Trois facons de reconnaitre la saisie par champ : le fragment n'importe ou
104
+ * (`regexp`), la valeur entiere, et le debut. La derniere remonte en tete des
105
+ * listes ordonnees par pertinence.
106
+ */
107
+ export declare function rmFilterSearchClause(fields: RmFilterSearchField[], input: string): {
108
+ bool: {
109
+ should: any[];
110
+ };
111
+ };
@@ -0,0 +1,69 @@
1
+ const DEFAULT_SIZE = 5e3;
2
+ function wildcard(input) {
3
+ return ("*" + input + "*").normalize("NFD").replace(/[\u0300-\u036f]/g, "");
4
+ }
5
+ export function rmFilterOptionsQuery(spec) {
6
+ const size = spec.size ?? DEFAULT_SIZE;
7
+ const count = Array.isArray(spec.field) ? {
8
+ multi_terms: {
9
+ terms: spec.field.map((field) => ({ field })),
10
+ size,
11
+ order: { _key: "asc" }
12
+ }
13
+ } : { terms: { field: spec.field, size, order: { _key: "asc" } } };
14
+ return {
15
+ size: 0,
16
+ query: {
17
+ bool: {
18
+ must: [
19
+ {
20
+ query_string: {
21
+ // Plusieurs champs valent un OU : la ligne repond des qu'un seul
22
+ // d'entre eux porte la saisie. C'est ce qui rend les synonymes.
23
+ fields: spec.search,
24
+ query: wildcard(spec.input),
25
+ analyze_wildcard: true,
26
+ default_operator: "AND"
27
+ }
28
+ }
29
+ ]
30
+ }
31
+ },
32
+ aggs: { count }
33
+ };
34
+ }
35
+ export function rmFilterOptions(response) {
36
+ const buckets = response?.aggregations?.count?.buckets ?? [];
37
+ return buckets.map(
38
+ (bucket) => Array.isArray(bucket.key) ? { label: String(bucket.key[0]), value: String(bucket.key[1]) } : { label: String(bucket.key), value: String(bucket.key) }
39
+ );
40
+ }
41
+ export function rmFilterSearchClause(fields, input) {
42
+ return {
43
+ bool: {
44
+ should: fields.map(({ field, nested }) => {
45
+ const clause = {
46
+ bool: {
47
+ should: [
48
+ {
49
+ regexp: {
50
+ [`${field}.keyword`]: {
51
+ value: `.*${input}.*`,
52
+ flags: "ALL",
53
+ case_insensitive: true
54
+ }
55
+ }
56
+ },
57
+ { match_phrase: { [`${field}.keyword`]: input } },
58
+ { prefix: { [`${field}.keyword`]: input } }
59
+ ]
60
+ }
61
+ };
62
+ return (nested ?? []).reduceRight(
63
+ (query, path) => ({ nested: { path, query } }),
64
+ clause
65
+ );
66
+ })
67
+ }
68
+ };
69
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekkare/romulus",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
4
4
  "description": "Romulus Design System - Nuxt module by Tekkare",
5
5
  "license": "MIT",
6
6
  "type": "module",