@precisa-saude/fhir-ocr-utils 0.25.1 → 0.26.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/dist/cli.js +387 -19
- package/dist/index.cjs +254 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +260 -1
- package/dist/index.d.ts +260 -1
- package/dist/index.js +253 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
4
|
import { parseArgs } from "util";
|
|
5
|
-
import { exitWithError } from "@precisa-saude/fhir/cli-utils";
|
|
5
|
+
import { exitWithError as exitWithError3 } from "@precisa-saude/fhir/cli-utils";
|
|
6
6
|
|
|
7
|
-
// src/cli/commands/
|
|
8
|
-
import {
|
|
7
|
+
// src/cli/commands/check.ts
|
|
8
|
+
import {
|
|
9
|
+
exitWithError,
|
|
10
|
+
formatTable,
|
|
11
|
+
getInput,
|
|
12
|
+
outputJson,
|
|
13
|
+
outputText,
|
|
14
|
+
parseJson
|
|
15
|
+
} from "@precisa-saude/fhir/cli-utils";
|
|
9
16
|
|
|
10
17
|
// src/anchor.ts
|
|
11
18
|
import {
|
|
@@ -373,35 +380,371 @@ function getMatchedCodes(result) {
|
|
|
373
380
|
return result.matches.map((m) => m.code);
|
|
374
381
|
}
|
|
375
382
|
|
|
383
|
+
// src/extraction-to-lab-result.ts
|
|
384
|
+
import { loincToCode } from "@precisa-saude/fhir";
|
|
385
|
+
function flagFor(b) {
|
|
386
|
+
if (typeof b.value !== "number") return "";
|
|
387
|
+
if (typeof b.referenceMax === "number" && b.value > b.referenceMax) return "H";
|
|
388
|
+
if (typeof b.referenceMin === "number" && b.value < b.referenceMin) return "L";
|
|
389
|
+
return "";
|
|
390
|
+
}
|
|
391
|
+
function extractionToLabResult(biomarkers, options = {}) {
|
|
392
|
+
const reportId = options.reportId ?? "laudo-demo";
|
|
393
|
+
const userId = options.userId ?? "paciente-demo";
|
|
394
|
+
const collectionDate = options.collectionDate ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
395
|
+
const observations = biomarkers.flatMap((b) => {
|
|
396
|
+
const code = b.loinc ? loincToCode(b.loinc) : void 0;
|
|
397
|
+
if (!code) return [];
|
|
398
|
+
return [
|
|
399
|
+
{
|
|
400
|
+
biomarkerCode: code,
|
|
401
|
+
biomarkerName: b.name,
|
|
402
|
+
flag: flagFor(b),
|
|
403
|
+
...typeof b.referenceMax === "number" ? { referenceMax: b.referenceMax } : {},
|
|
404
|
+
...typeof b.referenceMin === "number" ? { referenceMin: b.referenceMin } : {},
|
|
405
|
+
reportId,
|
|
406
|
+
unit: b.unit,
|
|
407
|
+
value: b.value
|
|
408
|
+
}
|
|
409
|
+
];
|
|
410
|
+
});
|
|
411
|
+
return {
|
|
412
|
+
observations,
|
|
413
|
+
profile: { name: "Paciente de Demonstra\xE7\xE3o", userId },
|
|
414
|
+
report: {
|
|
415
|
+
collectionDate,
|
|
416
|
+
createdAt: `${collectionDate}T00:00:00Z`,
|
|
417
|
+
overallStatus: observations.some((o) => o.flag !== "") ? "ANORMAL" : "NORMAL",
|
|
418
|
+
processingStatus: "complete",
|
|
419
|
+
reportId,
|
|
420
|
+
userId
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// src/extraction-validator.ts
|
|
426
|
+
import { loincToCode as loincToCode2 } from "@precisa-saude/fhir";
|
|
427
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
428
|
+
function schemaErrors(raw) {
|
|
429
|
+
if (!isRecord(raw)) return ["n\xE3o \xE9 um objeto"];
|
|
430
|
+
const errors = [];
|
|
431
|
+
const { confidence, loinc, name, referenceMax, referenceMin, sourceText, unit, value } = raw;
|
|
432
|
+
if (typeof name !== "string" || name.length === 0) errors.push("`name` ausente ou vazio");
|
|
433
|
+
if (typeof sourceText !== "string" || sourceText.length === 0)
|
|
434
|
+
errors.push("`sourceText` ausente ou vazio");
|
|
435
|
+
if (typeof unit !== "string") errors.push("`unit` ausente");
|
|
436
|
+
if (typeof value !== "number" && typeof value !== "string") errors.push("`value` ausente");
|
|
437
|
+
if (typeof confidence !== "number" || confidence < 0 || confidence > 1)
|
|
438
|
+
errors.push("`confidence` fora de 0..1");
|
|
439
|
+
if (loinc !== void 0 && loinc !== null && typeof loinc !== "string")
|
|
440
|
+
errors.push("`loinc` n\xE3o \xE9 string nem null");
|
|
441
|
+
for (const [key, v] of [
|
|
442
|
+
["referenceMax", referenceMax],
|
|
443
|
+
["referenceMin", referenceMin]
|
|
444
|
+
]) {
|
|
445
|
+
if (v !== void 0 && v !== null && typeof v !== "number")
|
|
446
|
+
errors.push(`\`${key}\` n\xE3o \xE9 n\xFAmero nem null`);
|
|
447
|
+
}
|
|
448
|
+
return errors;
|
|
449
|
+
}
|
|
450
|
+
function allowedKeys(anchors) {
|
|
451
|
+
const allowed = /* @__PURE__ */ new Set();
|
|
452
|
+
for (const match of anchors.matches) {
|
|
453
|
+
allowed.add(match.code);
|
|
454
|
+
if (match.loinc) allowed.add(match.loinc);
|
|
455
|
+
}
|
|
456
|
+
return allowed;
|
|
457
|
+
}
|
|
458
|
+
function validateExtraction(raw, options = {}) {
|
|
459
|
+
const { anchors } = options;
|
|
460
|
+
if (!isRecord(raw)) {
|
|
461
|
+
return { accepted: [], errors: ["a sa\xEDda n\xE3o \xE9 um objeto JSON"], rejected: [], valid: false };
|
|
462
|
+
}
|
|
463
|
+
if (!Array.isArray(raw.biomarkers)) {
|
|
464
|
+
return {
|
|
465
|
+
accepted: [],
|
|
466
|
+
errors: ["`biomarkers` ausente ou n\xE3o \xE9 lista"],
|
|
467
|
+
rejected: [],
|
|
468
|
+
valid: false
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
const allowed = anchors ? allowedKeys(anchors) : void 0;
|
|
472
|
+
const accepted = [];
|
|
473
|
+
const rejected = [];
|
|
474
|
+
for (const entry of raw.biomarkers) {
|
|
475
|
+
const problems = schemaErrors(entry);
|
|
476
|
+
if (problems.length > 0) {
|
|
477
|
+
rejected.push({ detail: problems.join("; "), raw: entry, reason: "schema" });
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
const biomarker = entry;
|
|
481
|
+
if (allowed) {
|
|
482
|
+
const loinc = biomarker.loinc ?? void 0;
|
|
483
|
+
if (!loinc) {
|
|
484
|
+
rejected.push({
|
|
485
|
+
detail: `"${biomarker.name}" veio sem c\xF3digo LOINC`,
|
|
486
|
+
raw: entry,
|
|
487
|
+
reason: "not-anchored"
|
|
488
|
+
});
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const internalCode = loincToCode2(loinc);
|
|
492
|
+
const isAnchored = allowed.has(loinc) || internalCode !== void 0 && allowed.has(internalCode);
|
|
493
|
+
if (!isAnchored) {
|
|
494
|
+
rejected.push({
|
|
495
|
+
detail: `${loinc} n\xE3o foi ancorado no texto de origem`,
|
|
496
|
+
raw: entry,
|
|
497
|
+
reason: "not-anchored"
|
|
498
|
+
});
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
accepted.push(biomarker);
|
|
503
|
+
}
|
|
504
|
+
return { accepted, errors: [], rejected, valid: true };
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// src/cli/commands/check.ts
|
|
508
|
+
async function check(args, json, options = {}) {
|
|
509
|
+
const sourcePath = typeof options.source === "string" ? options.source : void 0;
|
|
510
|
+
const raw = parseJson(await getInput(args[0]), "Sa\xEDda do modelo n\xE3o \xE9 JSON v\xE1lido.");
|
|
511
|
+
const anchors = sourcePath ? findBiomarkersInText(await getInput(sourcePath)) : void 0;
|
|
512
|
+
const result = validateExtraction(raw, { anchors });
|
|
513
|
+
if (options.convert === true) {
|
|
514
|
+
if (!result.valid) {
|
|
515
|
+
exitWithError(`Sa\xEDda inv\xE1lida: ${result.errors.join("; ")}`);
|
|
516
|
+
}
|
|
517
|
+
const envelope = extractionToLabResult(result.accepted);
|
|
518
|
+
const semCodigo = result.accepted.length - envelope.observations.length;
|
|
519
|
+
if (semCodigo > 0) {
|
|
520
|
+
process.stderr.write(
|
|
521
|
+
`aviso: ${String(semCodigo)} grandeza(s) aprovada(s) ficaram de fora do envelope por n\xE3o terem c\xF3digo no cat\xE1logo
|
|
522
|
+
`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
if (envelope.observations.length === 0) {
|
|
526
|
+
process.stderr.write("aviso: nenhuma observa\xE7\xE3o no envelope\n");
|
|
527
|
+
}
|
|
528
|
+
outputJson(envelope);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (json) {
|
|
532
|
+
outputJson(result);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (!result.valid) {
|
|
536
|
+
exitWithError(`Sa\xEDda inv\xE1lida: ${result.errors.join("; ")}`);
|
|
537
|
+
}
|
|
538
|
+
if (result.accepted.length > 0) {
|
|
539
|
+
outputText(
|
|
540
|
+
formatTable(
|
|
541
|
+
["LOINC", "Nome", "Valor", "Unidade"],
|
|
542
|
+
result.accepted.map((b) => [
|
|
543
|
+
b.loinc ?? "\u2014",
|
|
544
|
+
b.name,
|
|
545
|
+
String(b.value),
|
|
546
|
+
b.unit === "" ? "\u2014" : b.unit
|
|
547
|
+
])
|
|
548
|
+
)
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
if (result.rejected.length > 0) {
|
|
552
|
+
outputText(`
|
|
553
|
+
Recusados: ${result.rejected.length}`);
|
|
554
|
+
for (const r of result.rejected) {
|
|
555
|
+
outputText(` [${r.reason}] ${r.detail}`);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const total = result.accepted.length + result.rejected.length;
|
|
559
|
+
outputText(
|
|
560
|
+
`
|
|
561
|
+
Aceitos: ${result.accepted.length} de ${total}${anchors ? "" : " (sem --source: ancoragem n\xE3o conferida)"}`
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
376
565
|
// src/cli/commands/codes.ts
|
|
566
|
+
import { getInput as getInput2, outputJson as outputJson2, outputText as outputText2 } from "@precisa-saude/fhir/cli-utils";
|
|
377
567
|
async function codes(args, json) {
|
|
378
|
-
const text = await
|
|
568
|
+
const text = await getInput2(args[0]);
|
|
379
569
|
const result = findBiomarkersInText(text);
|
|
380
570
|
const matched = getMatchedCodes(result);
|
|
381
571
|
if (json) {
|
|
382
|
-
|
|
572
|
+
outputJson2(matched);
|
|
383
573
|
return;
|
|
384
574
|
}
|
|
385
575
|
if (matched.length === 0) {
|
|
386
|
-
|
|
576
|
+
outputText2("Nenhum biomarcador encontrado no texto.");
|
|
387
577
|
return;
|
|
388
578
|
}
|
|
389
|
-
|
|
390
|
-
|
|
579
|
+
outputText2(matched.join("\n"));
|
|
580
|
+
outputText2(`
|
|
391
581
|
Total: ${matched.length} c\xF3digos encontrados`);
|
|
392
582
|
}
|
|
393
583
|
|
|
584
|
+
// src/cli/commands/extract.ts
|
|
585
|
+
import { exitWithError as exitWithError2, getInput as getInput3, outputJson as outputJson3 } from "@precisa-saude/fhir/cli-utils";
|
|
586
|
+
|
|
587
|
+
// src/extraction-schema.ts
|
|
588
|
+
var LAB_EXTRACTION_SCHEMA = {
|
|
589
|
+
$id: "https://fhir-brasil.dev.br/schemas/lab-extraction.json",
|
|
590
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
591
|
+
additionalProperties: false,
|
|
592
|
+
properties: {
|
|
593
|
+
biomarkers: {
|
|
594
|
+
description: "The measurements read from the report.",
|
|
595
|
+
items: {
|
|
596
|
+
additionalProperties: false,
|
|
597
|
+
properties: {
|
|
598
|
+
confidence: {
|
|
599
|
+
description: "Confidence in this reading, from 0 to 1.",
|
|
600
|
+
maximum: 1,
|
|
601
|
+
minimum: 0,
|
|
602
|
+
type: "number"
|
|
603
|
+
},
|
|
604
|
+
loinc: {
|
|
605
|
+
anyOf: [{ type: "string" }, { type: "null" }],
|
|
606
|
+
description: "A LOINC code from the allowed list, or null when none of them applies."
|
|
607
|
+
},
|
|
608
|
+
name: {
|
|
609
|
+
description: "The measurement name as the report prints it.",
|
|
610
|
+
type: "string"
|
|
611
|
+
},
|
|
612
|
+
referenceMax: {
|
|
613
|
+
anyOf: [{ type: "number" }, { type: "null" }],
|
|
614
|
+
description: "Upper bound of the range printed on the report, or null."
|
|
615
|
+
},
|
|
616
|
+
referenceMin: {
|
|
617
|
+
anyOf: [{ type: "number" }, { type: "null" }],
|
|
618
|
+
description: "Lower bound of the range printed on the report, or null."
|
|
619
|
+
},
|
|
620
|
+
sourceText: {
|
|
621
|
+
description: "The snippet of the report carrying this measurement and its value.",
|
|
622
|
+
type: "string"
|
|
623
|
+
},
|
|
624
|
+
unit: {
|
|
625
|
+
description: "Unit as the report prints it. Empty string when there is none.",
|
|
626
|
+
type: "string"
|
|
627
|
+
},
|
|
628
|
+
value: {
|
|
629
|
+
anyOf: [{ type: "number" }, { type: "string" }],
|
|
630
|
+
description: "Numeric value, or text for a qualitative result."
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
required: ["name", "value", "unit", "sourceText", "confidence"],
|
|
634
|
+
type: "object"
|
|
635
|
+
},
|
|
636
|
+
type: "array"
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
required: ["biomarkers"],
|
|
640
|
+
title: "Laboratory report extraction",
|
|
641
|
+
type: "object"
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
// src/llm-client.ts
|
|
645
|
+
function buildPrompt(text, allowed) {
|
|
646
|
+
return [
|
|
647
|
+
"Extract the laboratory results from the report below.",
|
|
648
|
+
"",
|
|
649
|
+
"Return JSON matching this schema, and nothing else:",
|
|
650
|
+
JSON.stringify(LAB_EXTRACTION_SCHEMA),
|
|
651
|
+
"",
|
|
652
|
+
"Use only LOINC codes from this list:",
|
|
653
|
+
allowed,
|
|
654
|
+
"",
|
|
655
|
+
"REPORT:",
|
|
656
|
+
text
|
|
657
|
+
].join("\n");
|
|
658
|
+
}
|
|
659
|
+
function stripFence(raw) {
|
|
660
|
+
const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(raw);
|
|
661
|
+
return (fenced?.[1] ?? raw).trim();
|
|
662
|
+
}
|
|
663
|
+
async function extractWithModel(text, options) {
|
|
664
|
+
const { apiKey, baseUrl, model, responseFormat, timeoutMs = 3e5 } = options;
|
|
665
|
+
const allowed = findBiomarkersInText(text).filteredReference;
|
|
666
|
+
const startedAt = Date.now();
|
|
667
|
+
const formatBody = (mode2) => JSON.stringify({
|
|
668
|
+
messages: [{ content: buildPrompt(text, allowed), role: "user" }],
|
|
669
|
+
model,
|
|
670
|
+
...mode2 === "json_object" ? { response_format: { type: "json_object" } } : {},
|
|
671
|
+
...mode2 === "json_schema" ? {
|
|
672
|
+
response_format: {
|
|
673
|
+
json_schema: { name: "lab_extraction", schema: LAB_EXTRACTION_SCHEMA, strict: true },
|
|
674
|
+
type: "json_schema"
|
|
675
|
+
}
|
|
676
|
+
} : {},
|
|
677
|
+
temperature: 0
|
|
678
|
+
});
|
|
679
|
+
const post = async (mode2) => fetch(`${baseUrl.replace(/\/$/, "")}/chat/completions`, {
|
|
680
|
+
body: formatBody(mode2),
|
|
681
|
+
headers: {
|
|
682
|
+
"content-type": "application/json",
|
|
683
|
+
...apiKey ? { authorization: `Bearer ${apiKey}` } : {}
|
|
684
|
+
},
|
|
685
|
+
method: "POST",
|
|
686
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
687
|
+
});
|
|
688
|
+
const mode = responseFormat ?? "auto";
|
|
689
|
+
let response = await post(mode === "auto" ? "json_schema" : mode);
|
|
690
|
+
if (!response.ok && mode === "auto" && response.status >= 400 && response.status < 500) {
|
|
691
|
+
response = await post("none");
|
|
692
|
+
}
|
|
693
|
+
if (!response.ok) {
|
|
694
|
+
throw new Error(`${String(response.status)} de ${baseUrl}: ${await response.text()}`);
|
|
695
|
+
}
|
|
696
|
+
const body = await response.json();
|
|
697
|
+
const raw = body.choices?.[0]?.message?.content ?? "";
|
|
698
|
+
const tookMs = Date.now() - startedAt;
|
|
699
|
+
try {
|
|
700
|
+
return { payload: JSON.parse(stripFence(raw)), raw, tookMs };
|
|
701
|
+
} catch {
|
|
702
|
+
throw new Error(`O modelo n\xE3o devolveu JSON analis\xE1vel. Resposta crua:
|
|
703
|
+
${raw.slice(0, 500)}`);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
// src/cli/commands/extract.ts
|
|
708
|
+
var DEFAULT_BASE_URL = "http://localhost:1234/v1";
|
|
709
|
+
async function extract(args, _json, options = {}) {
|
|
710
|
+
const model = typeof options.model === "string" ? options.model : void 0;
|
|
711
|
+
if (!model) {
|
|
712
|
+
exitWithError2("Informe o modelo com --model. Exemplo: --model qwen3.5-9b");
|
|
713
|
+
}
|
|
714
|
+
const baseUrl = typeof options["base-url"] === "string" ? options["base-url"] : DEFAULT_BASE_URL;
|
|
715
|
+
const keyEnv = typeof options["api-key-env"] === "string" ? options["api-key-env"] : void 0;
|
|
716
|
+
const apiKey = keyEnv ? process.env[keyEnv] : void 0;
|
|
717
|
+
if (keyEnv && !apiKey) {
|
|
718
|
+
exitWithError2(`A vari\xE1vel de ambiente ${keyEnv} est\xE1 vazia.`);
|
|
719
|
+
}
|
|
720
|
+
const rf = options["response-format"];
|
|
721
|
+
const modos = ["auto", "json_object", "json_schema", "none"];
|
|
722
|
+
if (rf !== void 0 && (typeof rf !== "string" || !modos.includes(rf))) {
|
|
723
|
+
exitWithError2(`--response-format aceita ${modos.join(", ")}.`);
|
|
724
|
+
}
|
|
725
|
+
const responseFormat = rf;
|
|
726
|
+
const text = await getInput3(args[0]);
|
|
727
|
+
try {
|
|
728
|
+
const result = await extractWithModel(text, { apiKey, baseUrl, model, responseFormat });
|
|
729
|
+
process.stderr.write(`modelo respondeu em ${String(result.tookMs)}ms
|
|
730
|
+
`);
|
|
731
|
+
outputJson3(result.payload);
|
|
732
|
+
} catch (err) {
|
|
733
|
+
exitWithError2(err instanceof Error ? err.message : String(err));
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
394
737
|
// src/cli/commands/find.ts
|
|
395
|
-
import { formatTable, getInput as
|
|
738
|
+
import { formatTable as formatTable2, getInput as getInput4, outputJson as outputJson4, outputText as outputText3 } from "@precisa-saude/fhir/cli-utils";
|
|
396
739
|
async function find(args, json) {
|
|
397
|
-
const text = await
|
|
740
|
+
const text = await getInput4(args[0]);
|
|
398
741
|
const result = findBiomarkersInText(text);
|
|
399
742
|
if (json) {
|
|
400
|
-
|
|
743
|
+
outputJson4(result);
|
|
401
744
|
return;
|
|
402
745
|
}
|
|
403
746
|
if (result.matches.length === 0) {
|
|
404
|
-
|
|
747
|
+
outputText3("Nenhum biomarcador encontrado no texto.");
|
|
405
748
|
return;
|
|
406
749
|
}
|
|
407
750
|
const rows = result.matches.map((m) => [
|
|
@@ -411,13 +754,19 @@ async function find(args, json) {
|
|
|
411
754
|
m.confidence.toFixed(2),
|
|
412
755
|
String(m.position)
|
|
413
756
|
]);
|
|
414
|
-
|
|
415
|
-
|
|
757
|
+
outputText3(formatTable2(["C\xF3digo", "LOINC", "Match", "Confian\xE7a", "Posi\xE7\xE3o"], rows));
|
|
758
|
+
outputText3(
|
|
416
759
|
`
|
|
417
760
|
Encontrados: ${result.stats.matchedCount} de ${result.stats.totalPatterns} padr\xF5es (${result.stats.scanTimeMs}ms)`
|
|
418
761
|
);
|
|
419
762
|
}
|
|
420
763
|
|
|
764
|
+
// src/cli/commands/schema.ts
|
|
765
|
+
import { outputJson as outputJson5 } from "@precisa-saude/fhir/cli-utils";
|
|
766
|
+
async function schema(_args, _json) {
|
|
767
|
+
outputJson5(LAB_EXTRACTION_SCHEMA);
|
|
768
|
+
}
|
|
769
|
+
|
|
421
770
|
// src/cli/index.ts
|
|
422
771
|
var HELP = `fhir-ocr \u2014 CLI do @precisa-saude/fhir-ocr-utils
|
|
423
772
|
|
|
@@ -426,6 +775,13 @@ Uso: fhir-ocr <comando> [op\xE7\xF5es]
|
|
|
426
775
|
Comandos:
|
|
427
776
|
find [arquivo] Encontrar biomarcadores em texto OCR
|
|
428
777
|
codes [arquivo] Extrair c\xF3digos de biomarcadores encontrados no texto
|
|
778
|
+
schema Imprimir o contrato de sa\xEDda esperado do modelo
|
|
779
|
+
extract [arquivo] Mandar o laudo a um modelo compat\xEDvel com OpenAI
|
|
780
|
+
(--model, --base-url, --api-key-env,
|
|
781
|
+
--response-format, se precisar for\xE7ar um modo)
|
|
782
|
+
check <saida.json> Conferir a sa\xEDda de um modelo contra o contrato
|
|
783
|
+
e contra a ancoragem (--source <laudo.txt>).
|
|
784
|
+
Com --convert, imprime o envelope do fhir-bio
|
|
429
785
|
|
|
430
786
|
Flags globais:
|
|
431
787
|
--json Sa\xEDda em formato JSON
|
|
@@ -435,21 +791,33 @@ Flags globais:
|
|
|
435
791
|
L\xEA de stdin quando nenhum arquivo \xE9 fornecido.
|
|
436
792
|
`;
|
|
437
793
|
var COMMANDS = {
|
|
794
|
+
check,
|
|
438
795
|
codes,
|
|
439
|
-
|
|
796
|
+
extract,
|
|
797
|
+
find,
|
|
798
|
+
schema
|
|
440
799
|
};
|
|
441
800
|
async function main() {
|
|
442
801
|
const { positionals, values } = parseArgs({
|
|
443
802
|
allowPositionals: true,
|
|
444
803
|
options: {
|
|
804
|
+
// `check` precisa do texto que foi ao modelo. Declarado aqui porque o
|
|
805
|
+
// `parseArgs` roda uma vez só, no topo, e opção não declarada vira
|
|
806
|
+
// booleana e joga o valor nos posicionais.
|
|
807
|
+
"api-key-env": { type: "string" },
|
|
808
|
+
"base-url": { type: "string" },
|
|
809
|
+
convert: { default: false, type: "boolean" },
|
|
445
810
|
help: { default: false, short: "h", type: "boolean" },
|
|
446
811
|
json: { default: false, type: "boolean" },
|
|
812
|
+
model: { type: "string" },
|
|
813
|
+
"response-format": { type: "string" },
|
|
814
|
+
source: { type: "string" },
|
|
447
815
|
version: { default: false, short: "v", type: "boolean" }
|
|
448
816
|
},
|
|
449
817
|
strict: false
|
|
450
818
|
});
|
|
451
819
|
if (values.version) {
|
|
452
|
-
process.stdout.write(`${"0.
|
|
820
|
+
process.stdout.write(`${"0.26.0"}
|
|
453
821
|
`);
|
|
454
822
|
return;
|
|
455
823
|
}
|
|
@@ -460,9 +828,9 @@ async function main() {
|
|
|
460
828
|
}
|
|
461
829
|
const handler = COMMANDS[command];
|
|
462
830
|
if (!handler) {
|
|
463
|
-
|
|
831
|
+
exitWithError3(`Comando desconhecido: ${command}
|
|
464
832
|
Use --help para ver os comandos dispon\xEDveis.`);
|
|
465
833
|
}
|
|
466
|
-
await handler(rest, Boolean(values.json));
|
|
834
|
+
await handler(rest, Boolean(values.json), values);
|
|
467
835
|
}
|
|
468
|
-
main().catch((err) =>
|
|
836
|
+
main().catch((err) => exitWithError3(err.message));
|