@precisa-saude/fhir-ocr-utils 0.38.1 → 0.38.3

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 CHANGED
@@ -33,6 +33,9 @@ var UNAMBIGUOUS_SHORT_NAMES = /* @__PURE__ */ new Set([
33
33
  "alt",
34
34
  "ast",
35
35
  "bun",
36
+ // Creatina quinase: Fleury e a Quest imprimem só "CK", e sem a sigla o
37
+ // modelo ficava sem âncora e encaixava o valor em outro exame (PRE-486).
38
+ "ck",
36
39
  "wbc",
37
40
  "rbc",
38
41
  "mcv",
@@ -163,6 +166,9 @@ var GENETIC_CONTEXT_PATTERNS = [
163
166
  /\bhomozigot/,
164
167
  /\bsequence change\b/
165
168
  ];
169
+ var SUBTYPE_AFTER = {
170
+ CK: /^[\s-]*mb\b/i
171
+ };
166
172
 
167
173
  // src/body-region.ts
168
174
  var WHOLE_BODY_COMPOSITION_CODES = /* @__PURE__ */ new Set([
@@ -287,18 +293,30 @@ function mentionsUrinalysisHeader(normalizedText) {
287
293
  return ANY_HEADER.test(normalizedText);
288
294
  }
289
295
  var URINALYSIS_SECTION_NAMES = /* @__PURE__ */ new Map([
296
+ // "Aspecto", "Densidade" e "Corpos Cetônicos" são as grafias dos laudos
297
+ // brasileiros, e "Leukocytes", "Erythrocytes" e "Eritrócitos" as do
298
+ // sedimento. Fora deste mapa, "Corpos Cetônicos" e "Leukocytes" casam
299
+ // exames de outro painel (o beta-hidroxibutirato, o leucograma), a linha
300
+ // conta como `foreign` e a seção acabava ali, levando junto todas as linhas
301
+ // de baixo, que voltavam para os códigos do sangue (PRE-486).
302
+ ["aspecto", "Appearance_Urine"],
290
303
  ["bacteria", "Bacteria_Urine"],
291
304
  ["bacterias", "Bacteria_Urine"],
292
305
  ["bilirrubina", "Bilirubin_Urine"],
293
306
  ["bilirubin", "Bilirubin_Urine"],
294
307
  ["cetonas", "Ketones_Urine"],
295
308
  ["color", "Color_Urine"],
309
+ ["corpos cetonicos", "Ketones_Urine"],
296
310
  ["cor", "Color_Urine"],
311
+ ["densidade", "SpecificGravity_Urine"],
312
+ ["eritrocitos", "RBC_Urine"],
313
+ ["erythrocytes", "RBC_Urine"],
297
314
  ["glicose", "Glucose_Urine"],
298
315
  ["glucose", "Glucose_Urine"],
299
316
  ["hemacias", "RBC_Urine"],
300
317
  ["ketones", "Ketones_Urine"],
301
318
  ["leucocitos", "Leukocytes_Urine"],
319
+ ["leukocytes", "Leukocytes_Urine"],
302
320
  ["nitrite", "Nitrite_Urine"],
303
321
  ["nitrito", "Nitrite_Urine"],
304
322
  ["ph", "pH_Urine"],
@@ -681,6 +699,9 @@ function findBiomarkersInText(ocrText) {
681
699
  if (qualifiedByBodyRegion(entry.code, before) || followedByPercent(entry.code, after)) {
682
700
  continue;
683
701
  }
702
+ if (SUBTYPE_AFTER[entry.code]?.test(after)) {
703
+ continue;
704
+ }
684
705
  if (girth && SKINFOLD_SITE_CODES.has(entry.code)) {
685
706
  continue;
686
707
  }
@@ -1239,7 +1260,7 @@ async function main() {
1239
1260
  strict: false
1240
1261
  });
1241
1262
  if (values.version) {
1242
- process.stdout.write(`${"0.38.1"}
1263
+ process.stdout.write(`${"0.38.3"}
1243
1264
  `);
1244
1265
  return;
1245
1266
  }
package/dist/index.cjs CHANGED
@@ -17,6 +17,9 @@ var UNAMBIGUOUS_SHORT_NAMES = /* @__PURE__ */ new Set([
17
17
  "alt",
18
18
  "ast",
19
19
  "bun",
20
+ // Creatina quinase: Fleury e a Quest imprimem só "CK", e sem a sigla o
21
+ // modelo ficava sem âncora e encaixava o valor em outro exame (PRE-486).
22
+ "ck",
20
23
  "wbc",
21
24
  "rbc",
22
25
  "mcv",
@@ -147,6 +150,9 @@ var GENETIC_CONTEXT_PATTERNS = [
147
150
  /\bhomozigot/,
148
151
  /\bsequence change\b/
149
152
  ];
153
+ var SUBTYPE_AFTER = {
154
+ CK: /^[\s-]*mb\b/i
155
+ };
150
156
 
151
157
  // src/body-region.ts
152
158
  var WHOLE_BODY_COMPOSITION_CODES = /* @__PURE__ */ new Set([
@@ -271,18 +277,30 @@ function mentionsUrinalysisHeader(normalizedText) {
271
277
  return ANY_HEADER.test(normalizedText);
272
278
  }
273
279
  var URINALYSIS_SECTION_NAMES = /* @__PURE__ */ new Map([
280
+ // "Aspecto", "Densidade" e "Corpos Cetônicos" são as grafias dos laudos
281
+ // brasileiros, e "Leukocytes", "Erythrocytes" e "Eritrócitos" as do
282
+ // sedimento. Fora deste mapa, "Corpos Cetônicos" e "Leukocytes" casam
283
+ // exames de outro painel (o beta-hidroxibutirato, o leucograma), a linha
284
+ // conta como `foreign` e a seção acabava ali, levando junto todas as linhas
285
+ // de baixo, que voltavam para os códigos do sangue (PRE-486).
286
+ ["aspecto", "Appearance_Urine"],
274
287
  ["bacteria", "Bacteria_Urine"],
275
288
  ["bacterias", "Bacteria_Urine"],
276
289
  ["bilirrubina", "Bilirubin_Urine"],
277
290
  ["bilirubin", "Bilirubin_Urine"],
278
291
  ["cetonas", "Ketones_Urine"],
279
292
  ["color", "Color_Urine"],
293
+ ["corpos cetonicos", "Ketones_Urine"],
280
294
  ["cor", "Color_Urine"],
295
+ ["densidade", "SpecificGravity_Urine"],
296
+ ["eritrocitos", "RBC_Urine"],
297
+ ["erythrocytes", "RBC_Urine"],
281
298
  ["glicose", "Glucose_Urine"],
282
299
  ["glucose", "Glucose_Urine"],
283
300
  ["hemacias", "RBC_Urine"],
284
301
  ["ketones", "Ketones_Urine"],
285
302
  ["leucocitos", "Leukocytes_Urine"],
303
+ ["leukocytes", "Leukocytes_Urine"],
286
304
  ["nitrite", "Nitrite_Urine"],
287
305
  ["nitrito", "Nitrite_Urine"],
288
306
  ["ph", "pH_Urine"],
@@ -665,6 +683,9 @@ function findBiomarkersInText(ocrText) {
665
683
  if (qualifiedByBodyRegion(entry.code, before) || followedByPercent(entry.code, after)) {
666
684
  continue;
667
685
  }
686
+ if (_optionalChain([SUBTYPE_AFTER, 'access', _7 => _7[entry.code], 'optionalAccess', _8 => _8.test, 'call', _9 => _9(after)])) {
687
+ continue;
688
+ }
668
689
  if (girth && SKINFOLD_SITE_CODES.has(entry.code)) {
669
690
  continue;
670
691
  }
@@ -892,8 +913,8 @@ function allowedKeys(anchors) {
892
913
  }
893
914
  function withScannedMethod(biomarker, anchors) {
894
915
  const code = biomarker.loinc ? _fhir.loincToCode.call(void 0, biomarker.loinc) : void 0;
895
- if (!code || !_optionalChain([_fhir.getDefinitionByCode.call(void 0, code), 'optionalAccess', _7 => _7.methodVariants, 'optionalAccess', _8 => _8.length])) return biomarker;
896
- const scanned = _optionalChain([anchors, 'access', _9 => _9.matches, 'access', _10 => _10.find, 'call', _11 => _11((m) => m.code === code), 'optionalAccess', _12 => _12.methodLoinc]);
916
+ if (!code || !_optionalChain([_fhir.getDefinitionByCode.call(void 0, code), 'optionalAccess', _10 => _10.methodVariants, 'optionalAccess', _11 => _11.length])) return biomarker;
917
+ const scanned = _optionalChain([anchors, 'access', _12 => _12.matches, 'access', _13 => _13.find, 'call', _14 => _14((m) => m.code === code), 'optionalAccess', _15 => _15.methodLoinc]);
897
918
  const loinc = _nullishCoalesce(scanned, () => ( _fhir.codeToLoinc.call(void 0, code)));
898
919
  return loinc && loinc !== biomarker.loinc ? { ...biomarker, loinc } : biomarker;
899
920
  }
@@ -983,7 +1004,7 @@ function buildPrompt(text, allowed) {
983
1004
  }
984
1005
  function stripFence(raw) {
985
1006
  const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(raw);
986
- return (_nullishCoalesce(_optionalChain([fenced, 'optionalAccess', _13 => _13[1]]), () => ( raw))).trim();
1007
+ return (_nullishCoalesce(_optionalChain([fenced, 'optionalAccess', _16 => _16[1]]), () => ( raw))).trim();
987
1008
  }
988
1009
  async function extractWithModel(text, options) {
989
1010
  const { apiKey, baseUrl, headers = {}, model, responseFormat, timeoutMs = 3e5 } = options;
@@ -1023,7 +1044,7 @@ async function extractWithModel(text, options) {
1023
1044
  throw new Error(`${String(response.status)} de ${baseUrl}: ${await response.text()}`);
1024
1045
  }
1025
1046
  const body = await response.json();
1026
- const raw = _nullishCoalesce(_optionalChain([body, 'access', _14 => _14.choices, 'optionalAccess', _15 => _15[0], 'optionalAccess', _16 => _16.message, 'optionalAccess', _17 => _17.content]), () => ( ""));
1047
+ const raw = _nullishCoalesce(_optionalChain([body, 'access', _17 => _17.choices, 'optionalAccess', _18 => _18[0], 'optionalAccess', _19 => _19.message, 'optionalAccess', _20 => _20.content]), () => ( ""));
1027
1048
  const tookMs = Date.now() - startedAt;
1028
1049
  try {
1029
1050
  return { payload: JSON.parse(stripFence(raw)), raw, tookMs };