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.
- package/LICENSE +21 -0
- package/README.md +298 -0
- package/README.ru.md +298 -0
- package/dist/bm25.d.ts +47 -0
- package/dist/bm25.js +86 -0
- package/dist/browser-shims/buffer.d.ts +30 -0
- package/dist/browser-shims/buffer.js +31 -0
- package/dist/browser-shims/crypto.d.ts +33 -0
- package/dist/browser-shims/crypto.js +45 -0
- package/dist/browser-shims/fs-promises.d.ts +13 -0
- package/dist/browser-shims/fs-promises.js +25 -0
- package/dist/browser-shims/fs.d.ts +14 -0
- package/dist/browser-shims/fs.js +24 -0
- package/dist/browser-shims/globals.d.ts +9 -0
- package/dist/browser-shims/globals.js +23 -0
- package/dist/browser-shims/path.d.ts +57 -0
- package/dist/browser-shims/path.js +65 -0
- package/dist/browser-shims/process.d.ts +22 -0
- package/dist/browser-shims/process.js +27 -0
- package/dist/browser.d.ts +9 -0
- package/dist/browser.js +12 -0
- package/dist/chunk.d.ts +15 -0
- package/dist/chunk.js +76 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +87 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.js +51 -0
- package/dist/med-pdf-nmo.browser.js +40413 -0
- package/dist/med-pdf-nmo.browser.mjs +40395 -0
- package/dist/normalize.d.ts +73 -0
- package/dist/normalize.js +477 -0
- package/dist/pdf.d.ts +35 -0
- package/dist/pdf.js +396 -0
- package/dist/predictor/config.d.ts +28 -0
- package/dist/predictor/config.js +26 -0
- package/dist/predictor/constants.d.ts +3 -0
- package/dist/predictor/constants.js +59 -0
- package/dist/predictor/runtime.d.ts +15 -0
- package/dist/predictor/runtime.js +59 -0
- package/dist/predictor/scorers/biomedical-symbols.d.ts +36 -0
- package/dist/predictor/scorers/biomedical-symbols.js +347 -0
- package/dist/predictor/scorers/coordinate-table.d.ts +82 -0
- package/dist/predictor/scorers/coordinate-table.js +1210 -0
- package/dist/predictor/scorers/direction.d.ts +71 -0
- package/dist/predictor/scorers/direction.js +345 -0
- package/dist/predictor/scorers/drug-dose.d.ts +6 -0
- package/dist/predictor/scorers/drug-dose.js +221 -0
- package/dist/predictor/scorers/exact-answer.d.ts +10 -0
- package/dist/predictor/scorers/exact-answer.js +75 -0
- package/dist/predictor/scorers/fibrosis-stage.d.ts +6 -0
- package/dist/predictor/scorers/fibrosis-stage.js +103 -0
- package/dist/predictor/scorers/focused.d.ts +40 -0
- package/dist/predictor/scorers/focused.js +204 -0
- package/dist/predictor/scorers/frequency.d.ts +10 -0
- package/dist/predictor/scorers/frequency.js +203 -0
- package/dist/predictor/scorers/numeric.d.ts +77 -0
- package/dist/predictor/scorers/numeric.js +1161 -0
- package/dist/predictor/scorers/recommendation-item.d.ts +27 -0
- package/dist/predictor/scorers/recommendation-item.js +469 -0
- package/dist/predictor/scorers/search.d.ts +41 -0
- package/dist/predictor/scorers/search.js +515 -0
- package/dist/predictor/selection.d.ts +30 -0
- package/dist/predictor/selection.js +370 -0
- package/dist/predictor/text-utils.d.ts +49 -0
- package/dist/predictor/text-utils.js +497 -0
- package/dist/predictor/types.d.ts +23 -0
- package/dist/predictor/types.js +1 -0
- package/dist/predictor.d.ts +52 -0
- package/dist/predictor.js +3834 -0
- package/package.json +82 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { predict } from "./predictor.js";
|
|
5
|
+
function parseArgs(argv) {
|
|
6
|
+
const args = {};
|
|
7
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
8
|
+
const arg = argv[i];
|
|
9
|
+
if (!arg.startsWith("--"))
|
|
10
|
+
continue;
|
|
11
|
+
const key = arg.slice(2);
|
|
12
|
+
const next = argv[i + 1];
|
|
13
|
+
if (key === "answer") {
|
|
14
|
+
args.answer ??= [];
|
|
15
|
+
args.answer.push(next);
|
|
16
|
+
i += 1;
|
|
17
|
+
}
|
|
18
|
+
else if (next && !next.startsWith("--")) {
|
|
19
|
+
args[key] = next;
|
|
20
|
+
i += 1;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
args[key] = true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return args;
|
|
27
|
+
}
|
|
28
|
+
function parseAnswers(args) {
|
|
29
|
+
if (args.answers) {
|
|
30
|
+
const parsed = JSON.parse(args.answers);
|
|
31
|
+
if (!Array.isArray(parsed))
|
|
32
|
+
throw new Error("--answers must be a JSON array");
|
|
33
|
+
return parsed;
|
|
34
|
+
}
|
|
35
|
+
if (args.answer?.length) {
|
|
36
|
+
return args.answer.map((value, index) => {
|
|
37
|
+
const separator = value.indexOf("=");
|
|
38
|
+
if (separator > 0) {
|
|
39
|
+
return { id: value.slice(0, separator), text: value.slice(separator + 1) };
|
|
40
|
+
}
|
|
41
|
+
return { id: String.fromCharCode(65 + index), text: value };
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
throw new Error("Provide --answers JSON or one or more --answer ID=text arguments");
|
|
45
|
+
}
|
|
46
|
+
async function readInput(args) {
|
|
47
|
+
if (args.input) {
|
|
48
|
+
const inputPath = path.resolve(args.input);
|
|
49
|
+
const raw = await fs.readFile(inputPath, "utf8");
|
|
50
|
+
return JSON.parse(raw.replace(/^\uFEFF/, ""));
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
pdfPath: args.pdf ?? args.pdfPath,
|
|
54
|
+
question: args.question,
|
|
55
|
+
answers: parseAnswers(args),
|
|
56
|
+
mode: args.mode ?? "single",
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function attachLocalPdfData(input) {
|
|
60
|
+
if (input.pdfData || input.pdfBuffer || input.pdf || input.file || input.blob || input.pdfUrl || input.url || !input.pdfPath)
|
|
61
|
+
return input;
|
|
62
|
+
const absolutePath = path.resolve(input.pdfPath);
|
|
63
|
+
return {
|
|
64
|
+
...input,
|
|
65
|
+
pdfData: await fs.readFile(absolutePath),
|
|
66
|
+
cacheKey: absolutePath,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function main() {
|
|
70
|
+
const args = parseArgs(process.argv.slice(2));
|
|
71
|
+
if (args.help || args.h) {
|
|
72
|
+
process.stdout.write([
|
|
73
|
+
"Usage:",
|
|
74
|
+
" npm run predict -- --input request.json",
|
|
75
|
+
" npm run predict -- --pdf doc.pdf --question \"...\" --mode single --answer A=\"...\" --answer B=\"...\"",
|
|
76
|
+
"",
|
|
77
|
+
].join("\n"));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const input = await attachLocalPdfData(await readInput(args));
|
|
81
|
+
const output = await predict(input);
|
|
82
|
+
process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
|
|
83
|
+
}
|
|
84
|
+
main().catch((error) => {
|
|
85
|
+
process.stderr.write(`${error.stack || error.message}\n`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { predict, clearPredictorCache } from "./predictor.js";
|
|
2
|
+
import { setPdfJsLib } from "./pdf.js";
|
|
3
|
+
export { predict, clearPredictorCache, setPdfJsLib };
|
|
4
|
+
/**
|
|
5
|
+
* PDF-вход, который принимает высокоуровневый API.
|
|
6
|
+
*
|
|
7
|
+
* В Node.js обычно передается Buffer/Uint8Array или ArrayBuffer. В браузере
|
|
8
|
+
* можно передавать File или Blob. Строка считается URL и загружается через
|
|
9
|
+
* fetch.
|
|
10
|
+
*/
|
|
11
|
+
export type PdfInput = string | ArrayBuffer | Uint8Array | Blob | {
|
|
12
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Вариант ответа, который принимает {@link answerQuestion}.
|
|
16
|
+
*
|
|
17
|
+
* Для строковых вариантов автоматически создаются id: A, B, C и далее.
|
|
18
|
+
* Объектные варианты могут передавать стабильные id, которые вернутся в
|
|
19
|
+
* `selectedIds`.
|
|
20
|
+
*/
|
|
21
|
+
export type AnswerVariant = string | {
|
|
22
|
+
id?: string | number;
|
|
23
|
+
text?: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Параметры ответа на один вопрос НМО по одному PDF с медицинскими
|
|
27
|
+
* рекомендациями.
|
|
28
|
+
*/
|
|
29
|
+
export interface AnswerQuestionOptions {
|
|
30
|
+
/** Текст вопроса из задания НМО. */
|
|
31
|
+
question: string;
|
|
32
|
+
/** Варианты ответа. Алиас: `answers`. */
|
|
33
|
+
variants?: AnswerVariant[];
|
|
34
|
+
/** Альтернативное имя для `variants`. */
|
|
35
|
+
answers?: AnswerVariant[];
|
|
36
|
+
/** Режим вопроса: один ответ или точное множество нескольких ответов. */
|
|
37
|
+
type?: "single" | "multi" | string;
|
|
38
|
+
/** Альтернативное имя для `type`. */
|
|
39
|
+
mode?: "single" | "multi" | string;
|
|
40
|
+
/** Необязательный ключ кеша для повторного использования текста PDF. */
|
|
41
|
+
cacheKey?: string;
|
|
42
|
+
/** Явно переданный модуль PDF.js, полезно для браузерного окружения. */
|
|
43
|
+
pdfjsLib?: any;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Высокоуровневый результат, который возвращает {@link answerQuestion}.
|
|
47
|
+
*/
|
|
48
|
+
export interface AnswerQuestionResult {
|
|
49
|
+
/** Тексты выбранных ответов. */
|
|
50
|
+
selected: string[];
|
|
51
|
+
/** ID выбранных ответов. */
|
|
52
|
+
selectedIds: string[];
|
|
53
|
+
/** Итоговый режим, использованный predictor. */
|
|
54
|
+
mode: string;
|
|
55
|
+
/** Относительная уверенность выбранного ответа или набора ответов. */
|
|
56
|
+
confidence: number;
|
|
57
|
+
/** Калиброванные и сырые score по каждому варианту. */
|
|
58
|
+
scores: Array<{
|
|
59
|
+
id: string;
|
|
60
|
+
variant: string;
|
|
61
|
+
score: number;
|
|
62
|
+
raw: number;
|
|
63
|
+
}>;
|
|
64
|
+
/** Исходный низкоуровневый результат predictor. */
|
|
65
|
+
raw: any;
|
|
66
|
+
/** Фрагменты PDF и evidence, использованные при скоринге. */
|
|
67
|
+
evidence: any;
|
|
68
|
+
/** Runtime-метаданные, например число страниц и признак необходимости OCR. */
|
|
69
|
+
meta: any;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Отвечает на один вопрос НМО по содержимому PDF с медицинскими рекомендациями.
|
|
73
|
+
*
|
|
74
|
+
* Удобная обертка принимает варианты строками или объектами `{ id, text }`,
|
|
75
|
+
* вызывает локальный non-LLM predictor и сопоставляет выбранные id обратно с
|
|
76
|
+
* пользовательскими текстами ответов.
|
|
77
|
+
*
|
|
78
|
+
* @param pdf PDF-файл, байты, Blob/File, ArrayBuffer, Uint8Array или URL.
|
|
79
|
+
* @param options Текст вопроса, варианты ответа и необязательные runtime-настройки.
|
|
80
|
+
* @returns Выбранные ответы, id, confidence, score по вариантам и evidence.
|
|
81
|
+
*/
|
|
82
|
+
export declare function answerQuestion(pdf: PdfInput, options?: AnswerQuestionOptions): Promise<AnswerQuestionResult>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { predict, clearPredictorCache } from "./predictor.js";
|
|
2
|
+
import { setPdfJsLib } from "./pdf.js";
|
|
3
|
+
export { predict, clearPredictorCache, setPdfJsLib };
|
|
4
|
+
/**
|
|
5
|
+
* Отвечает на один вопрос НМО по содержимому PDF с медицинскими рекомендациями.
|
|
6
|
+
*
|
|
7
|
+
* Удобная обертка принимает варианты строками или объектами `{ id, text }`,
|
|
8
|
+
* вызывает локальный non-LLM predictor и сопоставляет выбранные id обратно с
|
|
9
|
+
* пользовательскими текстами ответов.
|
|
10
|
+
*
|
|
11
|
+
* @param pdf PDF-файл, байты, Blob/File, ArrayBuffer, Uint8Array или URL.
|
|
12
|
+
* @param options Текст вопроса, варианты ответа и необязательные runtime-настройки.
|
|
13
|
+
* @returns Выбранные ответы, id, confidence, score по вариантам и evidence.
|
|
14
|
+
*/
|
|
15
|
+
export async function answerQuestion(pdf, options = { question: "" }) {
|
|
16
|
+
const variants = options.variants ?? options.answers ?? [];
|
|
17
|
+
const answers = variants.map((item, index) => {
|
|
18
|
+
if (typeof item === "string") {
|
|
19
|
+
return { id: String.fromCharCode(65 + index), text: item };
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
id: String(item.id ?? String.fromCharCode(65 + index)),
|
|
23
|
+
text: String(item.text ?? ""),
|
|
24
|
+
};
|
|
25
|
+
});
|
|
26
|
+
const output = await predict({
|
|
27
|
+
pdfData: pdf,
|
|
28
|
+
cacheKey: options.cacheKey,
|
|
29
|
+
question: options.question,
|
|
30
|
+
answers,
|
|
31
|
+
mode: options.type ?? options.mode ?? "single",
|
|
32
|
+
}, { pdfjsLib: options.pdfjsLib });
|
|
33
|
+
const selectedAnswers = output.selected
|
|
34
|
+
.map((id) => answers.find((answer) => answer.id === id))
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
return {
|
|
37
|
+
selected: selectedAnswers.map((answer) => answer.text),
|
|
38
|
+
selectedIds: selectedAnswers.map((answer) => answer.id),
|
|
39
|
+
mode: output.mode,
|
|
40
|
+
confidence: output.confidence,
|
|
41
|
+
scores: answers.map((answer) => ({
|
|
42
|
+
id: answer.id,
|
|
43
|
+
variant: answer.text,
|
|
44
|
+
score: output.scores[answer.id] ?? 0,
|
|
45
|
+
raw: output.rawScores[answer.id] ?? 0,
|
|
46
|
+
})),
|
|
47
|
+
raw: output,
|
|
48
|
+
evidence: output.evidence,
|
|
49
|
+
meta: output.meta,
|
|
50
|
+
};
|
|
51
|
+
}
|