med-pdf-nmo 0.1.0

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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +298 -0
  3. package/README.ru.md +298 -0
  4. package/dist/bm25.d.ts +47 -0
  5. package/dist/bm25.js +86 -0
  6. package/dist/browser-shims/buffer.d.ts +30 -0
  7. package/dist/browser-shims/buffer.js +31 -0
  8. package/dist/browser-shims/crypto.d.ts +33 -0
  9. package/dist/browser-shims/crypto.js +45 -0
  10. package/dist/browser-shims/fs-promises.d.ts +13 -0
  11. package/dist/browser-shims/fs-promises.js +25 -0
  12. package/dist/browser-shims/fs.d.ts +14 -0
  13. package/dist/browser-shims/fs.js +24 -0
  14. package/dist/browser-shims/globals.d.ts +9 -0
  15. package/dist/browser-shims/globals.js +23 -0
  16. package/dist/browser-shims/path.d.ts +57 -0
  17. package/dist/browser-shims/path.js +65 -0
  18. package/dist/browser-shims/process.d.ts +22 -0
  19. package/dist/browser-shims/process.js +27 -0
  20. package/dist/browser.d.ts +9 -0
  21. package/dist/browser.js +12 -0
  22. package/dist/chunk.d.ts +15 -0
  23. package/dist/chunk.js +76 -0
  24. package/dist/cli.d.ts +2 -0
  25. package/dist/cli.js +87 -0
  26. package/dist/index.d.ts +82 -0
  27. package/dist/index.js +51 -0
  28. package/dist/med-pdf-nmo.browser.js +40413 -0
  29. package/dist/med-pdf-nmo.browser.mjs +40395 -0
  30. package/dist/normalize.d.ts +73 -0
  31. package/dist/normalize.js +477 -0
  32. package/dist/pdf.d.ts +35 -0
  33. package/dist/pdf.js +396 -0
  34. package/dist/predictor/config.d.ts +28 -0
  35. package/dist/predictor/config.js +26 -0
  36. package/dist/predictor/constants.d.ts +3 -0
  37. package/dist/predictor/constants.js +59 -0
  38. package/dist/predictor/runtime.d.ts +15 -0
  39. package/dist/predictor/runtime.js +59 -0
  40. package/dist/predictor/scorers/biomedical-symbols.d.ts +36 -0
  41. package/dist/predictor/scorers/biomedical-symbols.js +347 -0
  42. package/dist/predictor/scorers/coordinate-table.d.ts +82 -0
  43. package/dist/predictor/scorers/coordinate-table.js +1210 -0
  44. package/dist/predictor/scorers/direction.d.ts +71 -0
  45. package/dist/predictor/scorers/direction.js +345 -0
  46. package/dist/predictor/scorers/drug-dose.d.ts +6 -0
  47. package/dist/predictor/scorers/drug-dose.js +221 -0
  48. package/dist/predictor/scorers/exact-answer.d.ts +10 -0
  49. package/dist/predictor/scorers/exact-answer.js +75 -0
  50. package/dist/predictor/scorers/fibrosis-stage.d.ts +6 -0
  51. package/dist/predictor/scorers/fibrosis-stage.js +103 -0
  52. package/dist/predictor/scorers/focused.d.ts +40 -0
  53. package/dist/predictor/scorers/focused.js +204 -0
  54. package/dist/predictor/scorers/frequency.d.ts +10 -0
  55. package/dist/predictor/scorers/frequency.js +203 -0
  56. package/dist/predictor/scorers/numeric.d.ts +77 -0
  57. package/dist/predictor/scorers/numeric.js +1161 -0
  58. package/dist/predictor/scorers/recommendation-item.d.ts +27 -0
  59. package/dist/predictor/scorers/recommendation-item.js +469 -0
  60. package/dist/predictor/scorers/search.d.ts +41 -0
  61. package/dist/predictor/scorers/search.js +515 -0
  62. package/dist/predictor/selection.d.ts +30 -0
  63. package/dist/predictor/selection.js +370 -0
  64. package/dist/predictor/text-utils.d.ts +49 -0
  65. package/dist/predictor/text-utils.js +497 -0
  66. package/dist/predictor/types.d.ts +23 -0
  67. package/dist/predictor/types.js +1 -0
  68. package/dist/predictor.d.ts +52 -0
  69. package/dist/predictor.js +3834 -0
  70. package/package.json +82 -0
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Стоп-слова, которые удаляются из большинства поисковых token-запросов.
3
+ */
4
+ export declare const RUSSIAN_STOPWORDS: Set<string>;
5
+ /**
6
+ * Нормализует сырой текст PDF, вопроса или ответа перед поисковой обработкой.
7
+ *
8
+ * Обрабатывает Unicode-форму, пробелы, варианты тире, десятичные разделители и
9
+ * частые медицинские символы, сохраняя структуру текста для дальнейшей
10
+ * токенизации.
11
+ */
12
+ export declare function normalizeText(text: any): string;
13
+ /**
14
+ * Приводит визуально похожие кириллические и латинские символы к общей форме.
15
+ */
16
+ export declare function foldLookalikes(text: any): string;
17
+ /**
18
+ * Переводит текст в нормализованную поисковую форму для фразового и token-match.
19
+ */
20
+ export declare function normalizeForSearch(text: any): string;
21
+ /**
22
+ * Токенизирует текст для поиска и скоринга.
23
+ *
24
+ * @param text Сырой или нормализованный текст.
25
+ * @param options Переключатели фильтрации стоп-слов и стемминга.
26
+ * @returns Поисковые токены и безопасные расширения для составных/медицинских форм.
27
+ */
28
+ export declare function tokenize(text: any, { keepStopwords, stem }?: {
29
+ keepStopwords?: boolean;
30
+ stem?: boolean;
31
+ }): any[];
32
+ /**
33
+ * Токенизирует текст и удаляет дубликаты, сохраняя порядок первого появления.
34
+ */
35
+ export declare function uniqueTokens(text: any, options?: {}): any[];
36
+ /**
37
+ * Применяет легкий русский/медицинский suffix-стеммер для эвристического
38
+ * predictor.
39
+ */
40
+ export declare function stemToken(token: any): any;
41
+ /**
42
+ * Извлекает числовые токены и поддержанные русские числительные из текста.
43
+ */
44
+ export declare function extractNumbers(text: any): any[];
45
+ /**
46
+ * Определяет общие признаки вопроса, которые используют downstream scorers.
47
+ */
48
+ export declare function detectQuestionIntent(question: any): {
49
+ negative: boolean;
50
+ exception: boolean;
51
+ numeric: boolean;
52
+ listLike: boolean;
53
+ };
54
+ /**
55
+ * Считает Jaccard similarity для двух массивов токенов.
56
+ */
57
+ export declare function jaccard(a: any, b: any): number;
58
+ /**
59
+ * Доля уникальных токенов запроса, найденных среди токенов документа.
60
+ */
61
+ export declare function coverage(queryTokens: any, documentTokens: any): number;
62
+ /**
63
+ * Экранирует текст для безопасного использования как literal-фрагмент RegExp.
64
+ */
65
+ export declare function escapeRegExp(value: any): any;
66
+ /**
67
+ * Токенизирует фразу без стемминга и удаления стоп-слов.
68
+ */
69
+ export declare function phraseTokens(text: any): any[];
70
+ /**
71
+ * Создает мягкий whitespace-tolerant RegExp для нормализованной фразы.
72
+ */
73
+ export declare function phrasePattern(text: any): RegExp;
@@ -0,0 +1,477 @@
1
+ const CYRILLIC_LOOKALIKES = new Map([
2
+ ["а", "a"],
3
+ ["в", "b"],
4
+ ["е", "e"],
5
+ ["к", "k"],
6
+ ["м", "m"],
7
+ ["н", "h"],
8
+ ["о", "o"],
9
+ ["р", "p"],
10
+ ["с", "c"],
11
+ ["т", "t"],
12
+ ["у", "y"],
13
+ ["х", "x"],
14
+ ]);
15
+ const LATIN_LOOKALIKES = new Map([
16
+ ["a", "a"],
17
+ ["b", "b"],
18
+ ["c", "c"],
19
+ ["e", "e"],
20
+ ["h", "h"],
21
+ ["k", "k"],
22
+ ["m", "m"],
23
+ ["o", "o"],
24
+ ["p", "p"],
25
+ ["t", "t"],
26
+ ["x", "x"],
27
+ ["y", "y"],
28
+ ]);
29
+ const DASHES = /[\u2010\u2011\u2012\u2013\u2014\u2015\u2212]/g;
30
+ const SPACES = /[\u00a0\u1680\u2000-\u200b\u202f\u205f\u3000]/g;
31
+ const NUMERIC_REFERENCE_MARKS = /\s*\[\s*\d+(?:\s*(?:[,;]|\u2010|\u2011|\u2012|\u2013|\u2014|\u2015|-)\s*\d+)*\s*\](?=\s*(?:[.,;:]|$))/g;
32
+ const SPACED_NUMERIC_REFERENCE_BEFORE_DOT = /\s*\[\s*\d+(?:\s+\d+)+\s*\](?=\s*\.)/g;
33
+ /**
34
+ * Стоп-слова, которые удаляются из большинства поисковых token-запросов.
35
+ */
36
+ export const RUSSIAN_STOPWORDS = new Set([
37
+ "а",
38
+ "без",
39
+ "более",
40
+ "был",
41
+ "была",
42
+ "были",
43
+ "было",
44
+ "быть",
45
+ "в",
46
+ "во",
47
+ "все",
48
+ "для",
49
+ "до",
50
+ "его",
51
+ "ее",
52
+ "если",
53
+ "же",
54
+ "за",
55
+ "и",
56
+ "из",
57
+ "или",
58
+ "им",
59
+ "их",
60
+ "к",
61
+ "как",
62
+ "ко",
63
+ "на",
64
+ "над",
65
+ "не",
66
+ "ни",
67
+ "но",
68
+ "о",
69
+ "об",
70
+ "от",
71
+ "по",
72
+ "под",
73
+ "при",
74
+ "с",
75
+ "со",
76
+ "так",
77
+ "также",
78
+ "то",
79
+ "у",
80
+ "что",
81
+ "это",
82
+ "является",
83
+ "являются",
84
+ "следует",
85
+ "следующие",
86
+ "следующим",
87
+ "следующих",
88
+ "выделяют",
89
+ "относят",
90
+ "относится",
91
+ "составляет",
92
+ "составляют",
93
+ "характеризуется",
94
+ "имеет",
95
+ "имеют",
96
+ ]);
97
+ const IMPORTANT_SHORT = new Set([
98
+ "а",
99
+ "b",
100
+ "c",
101
+ "d",
102
+ "e",
103
+ "h",
104
+ "k",
105
+ "m",
106
+ "p",
107
+ "q",
108
+ "s",
109
+ "t",
110
+ "y",
111
+ "a1",
112
+ "a2",
113
+ "a3",
114
+ "b1",
115
+ "b2",
116
+ "b3",
117
+ "c1",
118
+ "c2",
119
+ "c3",
120
+ "t1",
121
+ "t2",
122
+ "n1",
123
+ "n2",
124
+ "m1",
125
+ ]);
126
+ const STEM_SUFFIXES = [
127
+ "иями",
128
+ "ями",
129
+ "ами",
130
+ "ости",
131
+ "ость",
132
+ "ение",
133
+ "ения",
134
+ "ений",
135
+ "иями",
136
+ "ический",
137
+ "ическая",
138
+ "ические",
139
+ "ического",
140
+ "ических",
141
+ "ически",
142
+ "ировать",
143
+ "ирован",
144
+ "ированн",
145
+ "ого",
146
+ "ему",
147
+ "ыми",
148
+ "ими",
149
+ "ыми",
150
+ "ими",
151
+ "ая",
152
+ "яя",
153
+ "ое",
154
+ "ее",
155
+ "ые",
156
+ "ие",
157
+ "ый",
158
+ "ий",
159
+ "ой",
160
+ "ых",
161
+ "их",
162
+ "ую",
163
+ "юю",
164
+ "ам",
165
+ "ям",
166
+ "ах",
167
+ "ях",
168
+ "ом",
169
+ "ем",
170
+ "ей",
171
+ "ой",
172
+ "ия",
173
+ "ие",
174
+ "ии",
175
+ "ию",
176
+ "ью",
177
+ "а",
178
+ "я",
179
+ "ы",
180
+ "и",
181
+ "е",
182
+ "у",
183
+ "ю",
184
+ ];
185
+ STEM_SUFFIXES.push("\u043e\u0432", "\u0435\u0432", "\u0438\u0447\u0435\u0441\u043a\u0443\u044e", "\u0438\u0447\u0435\u0441\u043a\u043e\u0439", "\u0438\u0447\u0435\u0441\u043a\u0438\u043c", "\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438", "\u0435\u0441\u043a\u0443\u044e", "\u0435\u0441\u043a\u043e\u0439", "\u0435\u0441\u043a\u0438\u043c", "\u0435\u0441\u043a\u0438\u043c\u0438");
186
+ const NUMBER_WORD_PAIRS = [
187
+ ["\u043d\u043e\u043b\u044c", "0"],
188
+ ["\u043e\u0434\u0438\u043d", "1"],
189
+ ["\u043e\u0434\u043d\u0430", "1"],
190
+ ["\u043e\u0434\u043d\u043e", "1"],
191
+ ["\u043e\u0434\u043d\u043e\u0433\u043e", "1"],
192
+ ["\u043e\u0434\u043d\u043e\u0439", "1"],
193
+ ["\u043e\u0434\u043d\u0438\u043c", "1"],
194
+ ["\u0434\u0432\u0430", "2"],
195
+ ["\u0434\u0432\u0435", "2"],
196
+ ["\u0434\u0432\u0443\u0445", "2"],
197
+ ["\u0434\u0432\u0443\u043c", "2"],
198
+ ["\u0442\u0440\u0438", "3"],
199
+ ["\u0442\u0440\u0435\u0445", "3"],
200
+ ["\u0442\u0440\u0435\u043c", "3"],
201
+ ["\u0447\u0435\u0442\u044b\u0440\u0435", "4"],
202
+ ["\u0447\u0435\u0442\u044b\u0440\u0435\u0445", "4"],
203
+ ["\u0447\u0435\u0442\u044b\u0440\u0435\u043c", "4"],
204
+ ["\u043f\u044f\u0442\u044c", "5"],
205
+ ["\u043f\u044f\u0442\u0438", "5"],
206
+ ["\u043f\u044f\u0442\u044c\u044e", "5"],
207
+ ["\u0448\u0435\u0441\u0442\u044c", "6"],
208
+ ["\u0448\u0435\u0441\u0442\u0438", "6"],
209
+ ["\u0448\u0435\u0441\u0442\u044c\u044e", "6"],
210
+ ["\u0441\u0435\u043c\u044c", "7"],
211
+ ["\u0441\u0435\u043c\u0438", "7"],
212
+ ["\u0441\u0435\u043c\u044c\u044e", "7"],
213
+ ["\u0432\u043e\u0441\u0435\u043c\u044c", "8"],
214
+ ["\u0432\u043e\u0441\u044c\u043c\u0438", "8"],
215
+ ["\u0432\u043e\u0441\u0435\u043c\u044c\u044e", "8"],
216
+ ["\u0434\u0435\u0432\u044f\u0442\u044c", "9"],
217
+ ["\u0434\u0435\u0432\u044f\u0442\u0438", "9"],
218
+ ["\u0434\u0435\u0432\u044f\u0442\u044c\u044e", "9"],
219
+ ["\u0434\u0435\u0441\u044f\u0442\u044c", "10"],
220
+ ["\u0434\u0435\u0441\u044f\u0442\u0438", "10"],
221
+ ["\u043e\u0434\u0438\u043d\u043d\u0430\u0434\u0446\u0430\u0442\u044c", "11"],
222
+ ["\u043e\u0434\u0438\u043d\u043d\u0430\u0434\u0446\u0430\u0442\u0438", "11"],
223
+ ["\u0434\u0432\u0435\u043d\u0430\u0434\u0446\u0430\u0442\u044c", "12"],
224
+ ["\u0434\u0432\u0435\u043d\u0430\u0434\u0446\u0430\u0442\u0438", "12"],
225
+ ];
226
+ let foldedNumberWords = null;
227
+ let foldedStemSuffixes = null;
228
+ /**
229
+ * Нормализует сырой текст PDF, вопроса или ответа перед поисковой обработкой.
230
+ *
231
+ * Обрабатывает Unicode-форму, пробелы, варианты тире, десятичные разделители и
232
+ * частые медицинские символы, сохраняя структуру текста для дальнейшей
233
+ * токенизации.
234
+ */
235
+ export function normalizeText(text) {
236
+ return String(text ?? "")
237
+ .normalize("NFKC")
238
+ .replace(SPACES, " ")
239
+ .replace(DASHES, "-")
240
+ .replace(SPACED_NUMERIC_REFERENCE_BEFORE_DOT, "")
241
+ .replace(NUMERIC_REFERENCE_MARKS, "")
242
+ .replace(/[\u2264\u2266]/g, " <= ")
243
+ .replace(/[\u2265\u2267]/g, " >= ")
244
+ .replace(/\u00a3(?=\s*\d)/g, " <= ")
245
+ .replace(/[βΒ]/g, " beta ")
246
+ .replace(/[αΑ]/g, " alpha ")
247
+ .replace(/[γΓ]/g, " gamma ")
248
+ .replace(/\u0430\u043b\u044c\u0444\u0430/giu, "alpha")
249
+ .replace(/\u0431\u0435\u0442\u0430/giu, "beta")
250
+ .replace(/\u0433\u0430\u043c\u043c\u0430/giu, "gamma")
251
+ .replace(/[“”„«»]/g, '"')
252
+ .replace(/[’‘`´]/g, "'")
253
+ .replace(/ё/g, "е")
254
+ .replace(/Ё/g, "Е")
255
+ .replace(/№/g, " no ")
256
+ .replace(/([0-9])\s*[xх×]\s*10/g, "$1x10")
257
+ .replace(/([0-9])\s*,\s*([0-9])/g, "$1.$2")
258
+ .replace(/([0-9])\s*-\s*([0-9])/g, "$1-$2")
259
+ .replace(/([а-я])([a-z0-9])/giu, "$1 $2")
260
+ .replace(/([a-z0-9])([а-я])/giu, "$1 $2")
261
+ .replace(/([a-zа-я])-([a-zа-я])/giu, "$1$2")
262
+ .replace(/([a-zа-я])-\s+([a-zа-я])/giu, "$1$2")
263
+ .replace(/\s+/g, " ")
264
+ .trim()
265
+ .toLowerCase();
266
+ }
267
+ /**
268
+ * Приводит визуально похожие кириллические и латинские символы к общей форме.
269
+ */
270
+ export function foldLookalikes(text) {
271
+ const source = normalizeText(text);
272
+ let out = "";
273
+ for (const ch of source) {
274
+ out += CYRILLIC_LOOKALIKES.get(ch) ?? LATIN_LOOKALIKES.get(ch) ?? ch;
275
+ }
276
+ return out;
277
+ }
278
+ /**
279
+ * Переводит текст в нормализованную поисковую форму для фразового и token-match.
280
+ */
281
+ export function normalizeForSearch(text) {
282
+ return foldLookalikes(text)
283
+ .replace(/([0-9])\s*%\s*/g, "$1% ")
284
+ .replace(/[^a-zа-я0-9%./+<>=-]+/giu, " ")
285
+ .replace(/\s+/g, " ")
286
+ .trim();
287
+ }
288
+ /**
289
+ * Токенизирует текст для поиска и скоринга.
290
+ *
291
+ * @param text Сырой или нормализованный текст.
292
+ * @param options Переключатели фильтрации стоп-слов и стемминга.
293
+ * @returns Поисковые токены и безопасные расширения для составных/медицинских форм.
294
+ */
295
+ export function tokenize(text, { keepStopwords = false, stem = true } = {}) {
296
+ const normalized = normalizeForSearch(text);
297
+ const tokens = normalized.match(/[a-zа-я0-9]+(?:[.%/+-][a-zа-я0-9]+)*/giu) ?? [];
298
+ const result = [];
299
+ for (const token of tokens) {
300
+ if (!token)
301
+ continue;
302
+ if (!keepStopwords && token.length > 1 && RUSSIAN_STOPWORDS.has(token))
303
+ continue;
304
+ if (!keepStopwords && token.length === 1 && !IMPORTANT_SHORT.has(token) && !/^\d$/.test(token))
305
+ continue;
306
+ const normalizedToken = stem ? stemToken(token) : token;
307
+ result.push(normalizedToken, ...expandToken(normalizedToken));
308
+ for (const part of compoundTokenParts(token)) {
309
+ const normalizedPart = stem ? stemToken(part) : part;
310
+ if (normalizedPart && normalizedPart !== normalizedToken) {
311
+ result.push(normalizedPart, ...expandToken(normalizedPart));
312
+ }
313
+ }
314
+ }
315
+ return result;
316
+ }
317
+ /**
318
+ * Токенизирует текст и удаляет дубликаты, сохраняя порядок первого появления.
319
+ */
320
+ export function uniqueTokens(text, options = {}) {
321
+ return [...new Set(tokenize(text, options))];
322
+ }
323
+ /**
324
+ * Применяет легкий русский/медицинский suffix-стеммер для эвристического
325
+ * predictor.
326
+ */
327
+ export function stemToken(token) {
328
+ if (/^[0-9]+(?:[./+-][0-9a-zа-я]+)*%?$/iu.test(token))
329
+ return token;
330
+ if (token.length <= 4)
331
+ return token;
332
+ for (const suffix of stemSuffixes()) {
333
+ if (token.length - suffix.length >= 4 && token.endsWith(suffix)) {
334
+ return token.slice(0, -suffix.length);
335
+ }
336
+ }
337
+ return token;
338
+ }
339
+ function stemSuffixes() {
340
+ if (!foldedStemSuffixes) {
341
+ foldedStemSuffixes = [...new Set(STEM_SUFFIXES.flatMap((suffix) => [suffix, foldLookalikes(suffix)]))]
342
+ .filter(Boolean)
343
+ .sort((a, b) => b.length - a.length);
344
+ }
345
+ return foldedStemSuffixes;
346
+ }
347
+ function compoundTokenParts(token) {
348
+ if (!/[a-zР°-СЏ].*[\/+].*[a-zР°-СЏ]/iu.test(token))
349
+ return [];
350
+ return token
351
+ .split(/[\/+]+/u)
352
+ .map((part) => part.trim())
353
+ .filter((part) => part.length > 1 && !/^\d+$/u.test(part));
354
+ }
355
+ const MGT_TOKEN = stemToken(foldLookalikes("\u043c\u0433\u0442"));
356
+ const HORMONOTHERAPY_TOKEN = stemToken(foldLookalikes("\u0433\u043e\u0440\u043c\u043e\u043d\u043e\u0442\u0435\u0440\u0430\u043f\u0438\u044f"));
357
+ const HORMONOTHERAPY_PART_TOKENS = [
358
+ "\u043c\u0435\u043d\u043e\u043f\u0430\u0443\u0437\u0430\u043b\u044c\u043d\u0430\u044f",
359
+ "\u0433\u043e\u0440\u043c\u043e\u043d\u0430\u043b\u044c\u043d\u0430\u044f",
360
+ "\u0442\u0435\u0440\u0430\u043f\u0438\u044f",
361
+ ].map((item) => stemToken(foldLookalikes(item)));
362
+ const SPYA_TOKEN = stemToken(foldLookalikes("\u0441\u043f\u044f"));
363
+ const SPYA_PART_TOKENS = [
364
+ "\u0441\u0438\u043d\u0434\u0440\u043e\u043c",
365
+ "\u043f\u043e\u043b\u0438\u043a\u0438\u0441\u0442\u043e\u0437\u043d\u044b\u0445",
366
+ "\u044f\u0438\u0447\u043d\u0438\u043a\u043e\u0432",
367
+ ].map((item) => stemToken(foldLookalikes(item)));
368
+ const ENDOMETRIAL_CANCER_ABBR_TOKEN = stemToken(foldLookalikes("\u0440\u044d"));
369
+ const ENDOMETRIAL_CANCER_PART_TOKENS = ["\u0440\u0430\u043a", "\u044d\u043d\u0434\u043e\u043c\u0435\u0442\u0440\u0438\u044f"].map((item) => stemToken(foldLookalikes(item)));
370
+ function expandToken(token) {
371
+ const out = [];
372
+ if (token === MGT_TOKEN || token === HORMONOTHERAPY_TOKEN) {
373
+ out.push(...HORMONOTHERAPY_PART_TOKENS, HORMONOTHERAPY_TOKEN);
374
+ }
375
+ if (token === SPYA_TOKEN)
376
+ out.push(...SPYA_PART_TOKENS);
377
+ if (token === ENDOMETRIAL_CANCER_ABBR_TOKEN) {
378
+ out.push(...ENDOMETRIAL_CANCER_PART_TOKENS, ENDOMETRIAL_CANCER_ABBR_TOKEN);
379
+ }
380
+ if (/средн.*тяж/.test(token))
381
+ out.push("средн", "тяжел", "тяжест");
382
+ if (/легк/.test(token))
383
+ out.push("легк");
384
+ if (/тяжел|тяжест/.test(token))
385
+ out.push("тяжел", "тяжест");
386
+ if (/крайн.*тяж/.test(token))
387
+ out.push("крайн", "тяжел", "тяжест");
388
+ if (/умерен/.test(token))
389
+ out.push("средн");
390
+ return out.filter((item) => item !== token);
391
+ }
392
+ /**
393
+ * Извлекает числовые токены и поддержанные русские числительные из текста.
394
+ */
395
+ export function extractNumbers(text) {
396
+ const normalized = normalizeForSearch(text);
397
+ const numeric = normalized.match(/\d+(?:[.,]\d+)?(?:-\d+(?:[.,]\d+)?)?%?/g) ?? [];
398
+ const wordNumbers = (normalized.match(/[a-zа-я]+/giu) ?? [])
399
+ .map((token) => numberWordValue(token) ?? numberWordValue(stemToken(token)))
400
+ .filter(Boolean);
401
+ return [...numeric, ...wordNumbers];
402
+ }
403
+ function numberWordValue(token) {
404
+ if (!foldedNumberWords) {
405
+ foldedNumberWords = new Map();
406
+ for (const [word, value] of NUMBER_WORD_PAIRS) {
407
+ const folded = foldLookalikes(word);
408
+ foldedNumberWords.set(folded, value);
409
+ foldedNumberWords.set(stemToken(folded), value);
410
+ }
411
+ }
412
+ return foldedNumberWords.get(String(token ?? "").toLowerCase());
413
+ }
414
+ /**
415
+ * Определяет общие признаки вопроса, которые используют downstream scorers.
416
+ */
417
+ export function detectQuestionIntent(question) {
418
+ const q = normalizeForSearch(question);
419
+ const raw = normalizeText(question);
420
+ const negative = /\b(не|нет|кроме|исключая|исключить|неверно|ошибочно|противопоказан|неправильн)\b/.test(q) ||
421
+ /не\s+(?:относ|явля|рекоменду|показан|характериз|следует|применя)/.test(q);
422
+ const exception = /\b(кроме|исключая|за\s+исключением|все\s+кроме)\b/.test(q);
423
+ const numeric = extractNumbers(question).length > 0 || /\b(доз|процент|лет|мг|мл|мм|балл|сут|час|недель|дней)\b/.test(q);
424
+ const listLike = /(относятся|включа|выделяют|являются|следующие|применяются|проводят|рекомендуются|показан|критериями|факторами)/.test(raw) ||
425
+ /^к\s+.+\s+относятся/u.test(raw);
426
+ return { negative, exception, numeric, listLike };
427
+ }
428
+ /**
429
+ * Считает Jaccard similarity для двух массивов токенов.
430
+ */
431
+ export function jaccard(a, b) {
432
+ if (!a.length || !b.length)
433
+ return 0;
434
+ const setA = new Set(a);
435
+ let hit = 0;
436
+ for (const token of new Set(b)) {
437
+ if (setA.has(token))
438
+ hit += 1;
439
+ }
440
+ return hit / Math.max(setA.size, new Set(b).size);
441
+ }
442
+ /**
443
+ * Доля уникальных токенов запроса, найденных среди токенов документа.
444
+ */
445
+ export function coverage(queryTokens, documentTokens) {
446
+ if (!queryTokens.length || !documentTokens.length)
447
+ return 0;
448
+ const doc = new Set(documentTokens);
449
+ let hit = 0;
450
+ const uniq = [...new Set(queryTokens)];
451
+ for (const token of uniq) {
452
+ if (doc.has(token))
453
+ hit += 1;
454
+ }
455
+ return hit / uniq.length;
456
+ }
457
+ /**
458
+ * Экранирует текст для безопасного использования как literal-фрагмент RegExp.
459
+ */
460
+ export function escapeRegExp(value) {
461
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
462
+ }
463
+ /**
464
+ * Токенизирует фразу без стемминга и удаления стоп-слов.
465
+ */
466
+ export function phraseTokens(text) {
467
+ return tokenize(text, { keepStopwords: true, stem: false });
468
+ }
469
+ /**
470
+ * Создает мягкий whitespace-tolerant RegExp для нормализованной фразы.
471
+ */
472
+ export function phrasePattern(text) {
473
+ const tokens = phraseTokens(text);
474
+ if (!tokens.length)
475
+ return null;
476
+ return new RegExp(tokens.map(escapeRegExp).join("\\s+"), "i");
477
+ }
package/dist/pdf.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Настраивает модуль PDF.js, который будет использовать runtime.
3
+ *
4
+ * В браузере это удобно вызывать, когда PDF.js загружен с CDN или через
5
+ * собственный bundler. В Node.js обычно достаточно пакетного импорта.
6
+ *
7
+ * @param pdfjsLib Объект модуля с методом `getDocument`.
8
+ */
9
+ export declare function setPdfJsLib(pdfjsLib: any): void;
10
+ /**
11
+ * Извлекает текст и легкие layout-метаданные из PDF.
12
+ *
13
+ * Экстрактор принимает байты, браузерные File/Blob, ArrayBuffer-подобные
14
+ * объекты или URL-строки. Возвращает текст страниц, строки, нормализованный
15
+ * текст и флаг `ocrNeeded`, если в PDF найдено подозрительно мало текста.
16
+ *
17
+ * @param pdfInput Байты PDF, File/Blob, ArrayBuffer, Uint8Array или URL.
18
+ * @param options Необязательный `cacheKey` и явно переданный `pdfjsLib`.
19
+ * @returns Текст страниц и метаданные, которые использует predictor.
20
+ */
21
+ export declare function extractPdfText(pdfInput: any, options?: any): Promise<{
22
+ pdfId: any;
23
+ cacheVersion: number;
24
+ pageCount: any;
25
+ extractedAt: string;
26
+ pages: any[];
27
+ ocrNeeded: boolean;
28
+ }>;
29
+ /**
30
+ * Очищает кеш извлечения PDF.
31
+ *
32
+ * Текущая browser-first реализация хранит текст PDF в runtime-кеше predictor,
33
+ * поэтому эта функция намеренно оставлена как совместимый no-op.
34
+ */
35
+ export declare function clearPdfMemoryCache(): void;