npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cariva/medical-sdk

v1.0.4

Published

Unified Cariva Medical SDK for Node.js and browsers - ASR, ICD Code, and more (single install, auto platform detection)

Readme

@cariva/medical-sdk

npm version License Node Browser

Turn a recorded consultation into a structured medical document. One install, same API on Node.js and in the browser — the right build is selected automatically.

Install · Quick start · Authentication · Modes · Options · process() · Result shapes · Errors · Audio · Diagnosis Code (ICD) · Migrating

Install

npm install @cariva/medical-sdk
<script src="https://unpkg.com/@cariva/[email protected]/dist/browser/index.umd.js"></script>
<script>
  const asr = new CarivaMedicalSDK.ASR({
    key: 'YOUR_KEY',
    secret: 'YOUR_SECRET',
    mode: 'doc-ipd-soap',
  });
  const icd = new CarivaMedicalSDK.ICD({
    key: 'YOUR_KEY',
    secret: 'YOUR_SECRET',
  });
</script>

The UMD build exposes window.CarivaMedicalSDK (.ASR, .ICD). Pin an exact version in production.

Quick start

import { ASR } from '@cariva/medical-sdk';

const asr = new ASR({
  key: 'YOUR_KEY',
  secret: 'YOUR_SECRET',
  mode: 'doc-ipd-soap',
  lang: 'th',
});

// audio: a File from <input>, a Blob from MediaRecorder, or file bytes on Node
const result = await asr.process(audio, {
  onProgress: p => console.log(`upload ${p.percentage}%`),
  onProcessing: step => console.log(step),
});

console.log(result.data.assessment); // typed as SOAPData

One process() call runs everything — compress, upload, poll — and resolves only when the note is ready. There is no task to track and nothing to poll yourself.

result.data is typed from the mode you passed — no type argument, no cast. When the mode is only known at runtime, switch on result.mode for the same narrowing:

const asr = new ASR({ key, secret, mode }); // `mode: ASRMode`, picked at runtime
const result = await asr.process(audio);

switch (result.mode) {
  case 'doc-ipd-soap':
    render(result.data.assessment); // SOAPData
    break;
  case 'radiology':
    render(result.data.impression); // RadiologyData
    break;
}

On Node, wrap the bytes in a Blob:

import { readFile } from 'node:fs/promises';

const audio = new Blob([await readFile('./consultation.wav')], { type: 'audio/wav' });

Authentication

Pass either key + secret or accessToken. Mixing the two, or passing neither, is a type error in TypeScript and a ValidationError at runtime.

new ASR({ key, secret, mode }); // API key + secret (HMAC)
new ASR({ accessToken, mode }); // bearer token

Credentials are issued per application — ask your Cariva contact if you do not have a pair yet.

Keep secrets out of client-side bundles — in the browser, prefer a short-lived accessToken issued by your own backend.

Modes

The mode decides which service runs and what result.data contains. Pick one when constructing.

| Mode | Produces | Result type | | ---------------------------------------- | ----------------------------------- | ----------------------------------------------- | | doc-ipd-soap | IPD SOAP note | SOAPData | | doc-opd-clinicalrecord | OPD clinical record | OPDClinicalRecordData | | nur-opd-nursenote-start / -end | Nursing note — start / end of shift | NurseNoteStartOfShiftData / …EndOfShiftData | | nur-opd-nursenote-faie-start / -end | Nursing note (FAIE) | NurseNoteFaieStartData / …FaieEndData | | nur-opd-nursenote-adpie-start / -end | Nursing note (ADPIE) | NurseNoteAdpieStartData / …AdpieEndData | | center-form | Center form | CenterFormData | | radiology | Radiology report | RadiologyData | | medical-mom | Minutes of a medical meeting | MedicalMOMData |

soap and medclerk also appear in autocomplete — legacy aliases of doc-ipd-soap and doc-opd-clinicalrecord. They still work; prefer the full names.

Options

Everything except mode, the credentials, and timeout can also be passed per call to process(), where it overrides the constructor value for that run.

| Option | Type | Default | Per call | Notes | | --------------- | ------------------------- | --------------- | :------: | -------------------------------------------------- | | mode | ASRMode | — (required) | | See Modes | | key secret | string | — | | HMAC credentials | | accessToken | string | — | | Bearer token, instead of key + secret | | lang | string | service default | ✓ | 'th', 'en', … | | specialty | ASRSpecialty | — | ✓ | Biases the model — see Specialties | | prefix | string | — | ✓ | Forwarded to the service with the task | | suggestion | boolean | — | ✓ | Forwarded to the service with the task | | newLineString | string \| null | null | ✓ | Replaces \n in the returned text | | taskPayload | Record<string, unknown> | — | ✓ | Extra fields for the create-task request | | timeout | number (ms) | 300000 | | Request timeout and polling limit (5 min) |

process(audio, options?)

Resolves with the completed result, or throws — see Errors. audio is a Blob or File. Beyond the options above it takes two callbacks:

| Callback | Fires | Argument | | -------------- | ------------- | ------------------------------------------------------ | | onProgress | During upload | { percentage, loaded, total } | | onProcessing | On each step | 'Preparing audio...', 'Uploading audio file...', … |

The result:

{
  mode: ASRMode; // the mode that produced this result — switch on it
  data: …; // that mode's payload — see Result shapes
  status: 'COMPLETED'; // anything else threw instead of resolving
  taskId: string;
  error?: string;
  progress?: number;
  createdAt?: string;
  updatedAt?: string;
  completedAt?: string;
}

data is not optional and status is not a union: process() resolves only on a completed task, so there is nothing to guard against at the call site. A completed task that arrives without a payload throws MISSING_RESULT_DATA instead of resolving with an empty result.

To name the result type — passing it between functions, storing it in state — use ASRResult<Mode>:

function render(result: ASRResult<'doc-ipd-soap'>) {
  result.data.assessment;
}

Result shapes

Fields marked ? may be absent.

// doc-ipd-soap
interface SOAPData {
  fastTranscription: string;
  subjective?: string;
  objective?: string;
  assessment?: string;
  plan?: string;
}

// doc-opd-clinicalrecord
interface OPDClinicalRecordData {
  fastTranscription: string;
  rawNote: string;
  chiefComplaint: string;
  presentIllness: string;
  diagnosis: string;
  pastMedicalHistory: string;
  familyHistory: string;
  personalHistory?: string;
  currentMedication?: string;
  physicalExamination: {
    generalAppearance: string;
    heent: string;
    cardiovascular: string;
    respiratory: string;
    gastrointestinal: string;
    neurological: string;
    vitalSign: string;
    other: string;
  };
  investigation: string;
  treatment: string;
  plan: string;
  recommendation?: string;
}
// nur-opd-nursenote-start  (NurseNoteFaieStartData has the same shape)
interface NurseNoteStartOfShiftData {
  fastTranscription: string;
  rawNote: string;
  focus: string;
  assessment: string;
  intervention: string;
  vitalSigns: VitalSign;
}

// nur-opd-nursenote-end        — vitalSigns is a plain string here
interface NurseNoteEndOfShiftData {
  fastTranscription: string;
  rawNote: string;
  evaluation: string;
  vitalSigns: string;
}

// nur-opd-nursenote-faie-end   — vitalSigns is a VitalSign object here
interface NurseNoteFaieEndData {
  fastTranscription: string;
  rawNote: string;
  evaluation: string;
  vitalSigns: VitalSign;
}

// nur-opd-nursenote-adpie-start
interface NurseNoteAdpieStartData {
  fastTranscription: string;
  rawNote: string;
  assessment: {
    objective: string;
    subjective: string;
  };
  diagnosis: string;
  planning: string;
  intervention: string;
}

// nur-opd-nursenote-adpie-end
interface NurseNoteAdpieEndData {
  fastTranscription: string;
  rawNote: string;
  evaluation: string;
}

// shared by the modes above
interface VitalSign {
  heartRate: string;
  respiratoryRate: string;
  bloodPressure: string;
  bodyTemperature: string;
  oxygenSaturation: string;
}
// radiology — fields are empty strings until the task completes
interface RadiologyData {
  modality: string;
  requestDetails: string;
  technique: string;
  findings: string;
  impression: string;
}
// medical-mom — a ward round, case conference, or handover written up as
// meeting minutes rather than as a patient record
interface MedicalMOMData {
  fastTranscription: string;
  rawNote: string;
  minutesOfMeeting: string;
}
// center-form
interface CenterFormData {
  [key: string]: unknown;
}

Need the text without knowing the shape? extractTranscript(result) returns the best available field, or '' — see Chaining.

Rating a result

Binary, like a thumbs up or down. Call it after process(); it rates the task that just finished.

await asr.rate(5);
await asr.rate(1, 'transcript cut off', ['RESULT_MISMATCH', 'PART_OF_THE_RESULT_IS_MISSING']);

| Parameter | Type | Notes | | ---------- | ------------------- | --------------------------------------------- | | score | 1 \| 5 | 5 positive, 1 negative — no scale between | | comment? | string | Free-text feedback | | reasons? | ASRRatingReason[] | Why it was downvoted |

| ASRRatingReason | Meaning | | ------------------------------- | ----------------- | | SLOW_PROCESSING | Took too long | | RESULT_MISMATCH | Incorrect content | | PART_OF_THE_RESULT_IS_MISSING | Missing details | | EXTRA_CONTENT_IN_RESULT | Extra content |

Instance API

await asr.cancel(); // stop the in-flight task and reset
await asr.create(); // cancel + reset, ready for the next recording

asr.taskId; // current task id ('' before the first task)
asr.status; // last known task status
asr.isProcessing; // true while a run is in flight

Cancelling makes the in-flight process() reject with ABORTED, so keep it inside the same try as the call.

Errors

Everything thrown extends CarivaError, which carries code, status, and detailsdetails holds the API's error payload, or the original exception when the SDK wrapped one.

| Class | Raised when | | ----------------- | ------------------------------------------------------------- | | ValidationError | Missing or conflicting credentials, missing mode, bad input | | ProcessingError | The audio could not be prepared, or the upload failed | | CarivaError | Everything else — API errors, timeouts, failed tasks |

Which call throws what:

| Call | Throws | | ---------------- | ---------------------------------------------------------------------------------- | | new ASR(...) | ValidationError for bad config — construct it where you can catch it | | process(audio) | Any of the codes below | | rate(score, …) | NO_TASK_ID before a task has completed, ValidationError for a score not 1 or 5 | | cancel() | Never — a server-side cancel that fails is a console warning |

import { CarivaError, ProcessingError, ValidationError } from '@cariva/medical-sdk';

try {
  await asr.process(audio);
} catch (err) {
  if (err instanceof ValidationError) {
    // bad config or input — a bug on your side, retrying will not help
  } else if (err instanceof ProcessingError) {
    // the audio never made it to the service
  } else if (err instanceof CarivaError) {
    console.log(err.code, err.status); // 'TRANSCRIPTION_FAILED', 500, …
  }
}

Branch on err.code when you need more than the class — it says which step failed:

| Code | Raised when | Worth retrying | | ------------------------- | ----------------------------------------------------------------------- | ------------------------------ | | VALIDATION_ERROR | Bad credentials, missing mode, bad argument | No — fix the call | | AUDIO_PROCESSING_FAILED | The recording could not be prepared for upload | No — the same file fails again | | asr/create-task-error | The service refused the task — quota, permission, or a rejected payload | Read err.status first | | TRANSCRIPTION_FAILED | The task ran and failed; err.message carries the service's reason | Rarely — usually the recording | | PROCESSING_TIMED_OUT | Still unfinished after timeout (5 minutes by default) | Yes, or raise timeout | | ABORTED | cancel() ended the run | You asked for it | | MISSING_RESULT_DATA | The task completed with no payload | Yes | | PROCESS_FAILED | Anything the pipeline did not classify, e.g. the upload failing | Yes |

The codes the SDK raises autocomplete on err.code; codes the API returns come through unchanged, so treat the list as the common cases rather than a closed set.

Specialties

Pass one as specialty to bias the model toward that domain. The list is autocomplete, not a whitelist — a value the service adds before the SDK knows about it is still accepted.

| Key | Label | | ------------------ | -------------------------------- | | gen-practitioner | General Practitioner (GP) | | gen-med | General Medicine | | med-cardio | Cardiology | | med-pulmonary | Pulmonology | | med-gastro | Gastroenterology (GI) | | med-neuro | Neurology | | med-nephro | Nephrology | | med-onco | Oncology | | med-endo | Endocrinology | | med-skin | Dermatology (Skin) | | med-allergy | Allergy and Immunology | | surgery | General Surgery | | orthopedic | Orthopedic | | gen-ped | General Pediatrics | | ophthalmology | Ophthalmology (Eye) | | otolaryngology | Otolaryngology (ENT) | | ob-gyn | Obstetrics & Gynecology (OB-GYN) | | emergency | Emergency (ER) | | other | Other specialists |

Audio handling

Recordings are normalised to mono 16 kHz MP3 at 48 kbps before upload — the format the recogniser consumes, and roughly a 29x reduction from a stereo 44.1 kHz WAV. Hand process() whatever you recorded; there is nothing to configure and no encoder to install. If any step fails the SDK falls back to a larger format and ultimately to the original file, so pre-processing never blocks a transcription.

A recording is uploaded untouched when there is nothing to gain from encoding it: when it is already mono at or below 16 kHz, or already recorded at a lower bitrate than the encoder would produce — which covers most phone voice memos.

ffmpeg on a Node server (optional)

npm install @cariva/medical-sdk is the whole installation. In the browser there is nothing more to think about. On a Node server, one optional addition is worth knowing about.

The encoder is pure JavaScript, so WAV compresses with no setup at all. Decoding an already-compressed recording — M4A, MP3, OGG, WebM — needs ffmpeg, which this SDK does not install. Without one, those formats upload exactly as they were recorded: the transcript is identical, the upload is just larger. Nothing errors and nothing warns.

Add ffmpeg when your server receives compressed audio that is not already mono at ≤16 kHz or ≤48 kbps — a phone M4A or a MediaRecorder WebM usually is not, so expect roughly 2–3x less bandwidth per upload. Install it in the image, not as a package:

RUN apk add --no-cache ffmpeg                    # Alpine
RUN apt-get update && apt-get install -y ffmpeg  # Debian/Ubuntu

The SDK finds it on PATH by itself — no configuration, no code change. Verify with ffmpeg -version.

If you cannot change the image (serverless, for instance), ffmpeg-static in your own dependencies works too, and the SDK picks it up the same way. Be aware that it downloads a 45 MB binary from an install script, and npm 12, pnpm 10+, and Bun all block those by default — so you will have to allow that build in your own project.

Diagnosis Code (ICD)

Turns a clinical note into ICD codes and looks codes up by keyword. The same ICD class handles ICD-10, ICD-9, and combined ICD-9+ICD-10 — select the system with the optional modes array on every call. When modes is omitted the default is ["icd10"], keeping backward-compatibility with the legacy ICD10 class.

import { ICD } from '@cariva/medical-sdk';

const icd = new ICD({ key: 'YOUR_KEY', secret: 'YOUR_SECRET', baseUrl: 'https://…' });

// ICD-10
const { primary } = await icd.getIcdOpdCode('runny nose and cough');
console.log(primary?.code, primary?.description); // J00  Acute nasopharyngitis

const { primary: p } = await icd.getIcdOpdCode(['icd10'], 'runny nose and cough');

// ICD-9
const { principal, secondary } = await icd.getIcdOpdCode(['icd9'], 'appendectomy');
console.log(principal?.code, principal?.orStatus); // 47.0  true

// Combined ICD-10 + ICD-9
const result = await icd.getIcdOpdCode(['icd10', 'icd9'], 'appendectomy');
console.log(result.icd10.primary?.code); // K35.9
console.log(result.icd9.principal?.orStatus); // true

The constructor takes the same key / secret or accessToken credentials as ASR. baseUrl is required (the ICD service has its own host, separate from ASR).

new ICD({ key, secret, baseUrl }); // API key + secret (HMAC)
new ICD({ accessToken, baseUrl }); // bearer token
new ICD({ key, secret, baseUrl, timeout: 60_000 }); // custom timeout (ms, default 300 000)

Modes

The first argument of every method is an optional modes array that selects the coding system(s). When omitted it defaults to ["icd10"]. Order does not matter["icd9","icd10"] is identical to ["icd10","icd9"].

| modes | Systems | Combined endpoint | | ---------------------- | --------------------------------- | ----------------- | | omitted or ["icd10"] | ICD-10 diagnosis codes | — | | ["icd9"] | ICD-9 procedure / diagnosis codes | — | | ["icd10","icd9"] | Both in one async task | ✓ (coding only) |

Search and suggest-block accept only a single system (["icd10"] or ["icd9"]). Passing ["icd10","icd9"] to those methods throws a ValidationError.

Which call do you need?

| You have | Call | You get back | | ----------------------------- | ------------------------------------------------------------------- | ------------------------------------- | | Outpatient note, as free text | getIcdOpdCode(modes?, note, options?) | see Coding results | | Inpatient record, in sections | getIcdIpdCode(modes?, records, options?) | see Coding results | | A keyword to look up | searchIcdOpd(modes?, query, options?) / searchIcdIpd | flat list of matches | | A code, and want related ones | suggestBlockIcdOpd(modes?, code, options?) / suggestBlockIcdIpd | flat list of related codes |

Outpatient and inpatient are permissioned and billed separately — use the correct pair for the encounter type.

Both coding methods are asynchronous tasks: the SDK creates the task, polls until it completes, and resolves with the result. The default timeout is 5 minutes (300 000 ms); pass a custom timeout to the constructor to change it.

Types

The return type is inferred from modes — no cast required. When modes is omitted the return type is always the ICD-10 variant.

// modes → inferred return type
icd.getIcdOpdCode(note); // Promise<ICD10OpdCodeResult>  (modes omitted)
icd.getIcdOpdCode(['icd10'], note); // Promise<ICD10OpdCodeResult>
icd.getIcdOpdCode(['icd9'], note); // Promise<ICD9CodeResult>
icd.getIcdOpdCode(['icd10', 'icd9'], note); // Promise<ICD9Icd10OpdCodeResult>

icd.searchIcdOpd(query); // Promise<ICD10SearchEntry[]>  (modes omitted)
icd.searchIcdOpd(['icd10'], query); // Promise<ICD10SearchEntry[]>
icd.searchIcdOpd(['icd9'], query); // Promise<ICD9Entry[]>

The generic utility types let you name the result in your own functions:

import type { ICDOpdCodeResult } from '@cariva/medical-sdk';

function render(r: ICDOpdCodeResult<['icd10']>) {
  r.primary?.code; // typed as ICD10OpdCodeResult
}

ICD-10 entry types

interface ICD10Entry {
  code: string; // 'J00'
  description: string; // 'Acute nasopharyngitis [common cold]'
}

interface ICD10SearchEntry extends ICD10Entry {
  display?: string;
}

ICD-9 entry type

interface ICD9Entry {
  code: string;
  description: string;
  orStatus?: boolean;
  drgStatus?: boolean;
}

Coding result types per mode

| modes | OPD result | IPD result | | ------------------ | ------------------------ | ------------------------ | | ["icd10"] | ICD10OpdCodeResult | ICD10IpdCodeResult | | ["icd9"] | ICD9CodeResult | ICD9CodeResult | | ["icd10","icd9"] | ICD9Icd10OpdCodeResult | ICD9Icd10IpdCodeResult |

Coding results

modes: ["icd10"] — outpatient

interface ICD10OpdCodeResult {
  primary: ICD10Entry | null;
  secondary: ICD10Entry[];
  externalCause: ICD10Entry[];
}

modes: ["icd10"] — inpatient

interface ICD10IpdCodeResult {
  principal: ICD10Entry | null;
  comorbidity: ICD10Entry[];
  complication: ICD10Entry[];
  otherDiagnosis: ICD10Entry[];
  externalCause: ICD10Entry[];
}

modes: ["icd9"] — outpatient or inpatient

interface ICD9CodeResult {
  principal: ICD9Entry | null;
  secondary: ICD9Entry[];
}

modes: ["icd10","icd9"] — outpatient

interface ICD9Icd10OpdCodeResult {
  icd9: {
    principal: ICD9Entry | null;
    secondary: ICD9Entry[];
  };
  icd10: {
    primary: ICD10Entry | null;
    secondary: ICD10Entry[];
    externalCause: ICD10Entry[];
  };
}

modes: ["icd10","icd9"] — inpatient

interface ICD9Icd10IpdCodeResult {
  icd9: {
    principal: ICD9Entry | null;
    secondary: ICD9Entry[];
  };
  icd10: {
    principal: ICD10Entry | null;
    comorbidity: ICD10Entry[];
    complication: ICD10Entry[];
    otherDiagnosis: ICD10Entry[];
    externalCause: ICD10Entry[];
  };
}

Coding an encounter

OPD: getIcdOpdCode(modes, note, options?)

// ICD-10 only
const { primary, secondary, externalCause } = await icd.getIcdOpdCode(
  ['icd10'],
  'Runny nose, cough and mild fever for two days.',
  { patientProfile: { age: 35, sex: 'female' } }
);
console.log(primary); // { code: 'J00', description: 'Acute nasopharyngitis [common cold]' }

// ICD-9 only
const { principal, secondary } = await icd.getIcdOpdCode(['icd9'], 'appendectomy');
console.log(principal); // { code: '47.0', description: 'Appendectomy', orStatus: true, drgStatus: false }

// Combined ICD-10 + ICD-9
const combined = await icd.getIcdOpdCode(['icd10', 'icd9'], 'appendectomy');
console.log(combined.icd10.primary?.code); // 'K35.9'
console.log(combined.icd9.principal?.orStatus); // true

IPD: getIcdIpdCode(modes, records, options?)

Input is the record split into named sections. Combined mode is supported here too:

const records = [
  { name: 'Chief complaint', content: 'Fever and cough for 3 days' },
  {
    name: 'Assessment',
    content: 'Community acquired pneumonia. Known type 2 diabetes. Fall at home.',
  },
];

// ICD-10
const { principal, comorbidity, complication, otherDiagnosis, externalCause } =
  await icd.getIcdIpdCode(['icd10'], records);
console.log(principal); // { code: 'J18.9', description: 'Pneumonia, unspecified' }
console.log(comorbidity); // [{ code: 'E11.9', description: 'Type 2 diabetes mellitus without complications' }]
console.log(complication); // [{ code: 'J96.00', description: 'Acute respiratory failure, unspecified whether with hypoxia or hypercapnia' }]
console.log(otherDiagnosis); // [{ code: 'E87.6', description: 'Hypokalaemia' }]
console.log(externalCause); // [{ code: 'W19.9', description: 'Unspecified fall, unspecified place' }]

// ICD-9
const { principal: icd9Principal, secondary } = await icd.getIcdIpdCode(['icd9'], records);
console.log(icd9Principal); // { code: '96.71', description: 'Continuous invasive mechanical ventilation for less than 96 consecutive hours', orStatus: false, drgStatus: true }
console.log(secondary); // []

// Combined
const both = await icd.getIcdIpdCode(['icd10', 'icd9'], records);
console.log(both.icd10.principal?.code); // 'J18.9'
console.log(both.icd10.comorbidity); // [{ code: 'E11.9', description: 'Type 2 diabetes mellitus without complications' }]
console.log(both.icd10.complication); // [{ code: 'J96.00', description: 'Acute respiratory failure, unspecified whether with hypoxia or hypercapnia' }]
console.log(both.icd10.otherDiagnosis); // [{ code: 'E87.6', description: 'Hypokalaemia' }]
console.log(both.icd10.externalCause); // [{ code: 'W19.9', description: 'Unspecified fall, unspecified place' }]
console.log(both.icd9.principal?.drgStatus); // true / false
console.log(both.icd9.secondary); // []

OptionsgetIcdOpdCode, getIcdIpdCode

| Option | Type | Notes | | ---------------- | ----------------------------- | --------------------------------------------------------------------------------------- | | patientProfile | { age?, sex?, isPregnant? } | age ≤ 0 is ignored. With isPregnant: true, sex is required and cannot be 'male' | | signal | AbortSignal | Cancels the in-flight task |

Searching by keyword

searchIcdOpd and searchIcdIpd perform a catalogue lookup — they are not coding a note, so there is no primary in the result. Pass ["icd10"] or ["icd9"]; combined is not supported.

// ICD-10 OPD search
const matches = await icd.searchIcdOpd(['icd10'], 'pneumonia', { searchBy: 'text' });
// [{ code: 'J18', description: 'Pneumonia', display: 'J18 Pneumonia' }]

// ICD-9 IPD search
const icd9matches = await icd.searchIcdIpd(['icd9'], '47');
// [{ code: '47.0', description: 'Appendectomy', orStatus: true, drgStatus: false }]

OptionssearchIcdOpd, searchIcdIpd

| Option | Type | Notes | | ---------- | --------------------------- | ------------------- | | searchBy | 'code' \| 'text' \| 'all' | Default 'all' | | signal | AbortSignal | Cancels the request |

Suggesting related codes

suggestBlockIcdOpd and suggestBlockIcdIpd take a code you already have and return codes commonly seen alongside it. Pass ["icd10"] or ["icd9"]; combined is not supported.

// ICD-10 OPD — hypertension → codes seen alongside it
const related = await icd.suggestBlockIcdOpd(['icd10'], 'I10');
// [{ code: 'I15.8', description: 'Other secondary hypertension' }]

// ICD-9 IPD — codes alongside an appendectomy
const icd9related = await icd.suggestBlockIcdIpd(['icd9'], '47.0');
// [{ code: '47.1', description: 'Incidental appendectomy', orStatus: true, drgStatus: false }]

OptionssuggestBlockIcdOpd, suggestBlockIcdIpd

| Option | Type | Notes | | -------- | ------------- | ------------------- | | signal | AbortSignal | Cancels the request |

ICD errors

Failures throw the same CarivaError family as ASR — see Errors. Blank input, an empty structuredRecords list, an invalid patientProfile, an empty modes array, an unknown system name, or a combined modes on a search/suggest call all throw ValidationError before any request goes out.

| Call | Error code | | ------------------------------------ | ------------------------------- | | getIcdOpdCode(['icd10'], …) | icd10/opd-code-error | | getIcdIpdCode(['icd10'], …) | icd10/ipd-code-error | | getIcdOpdCode(['icd9'], …) | icd9/opd-code-error | | getIcdIpdCode(['icd9'], …) | icd9/ipd-code-error | | getIcdOpdCode(['icd10','icd9'], …) | icd9-icd10/opd-code-error | | getIcdIpdCode(['icd10','icd9'], …) | icd9-icd10/ipd-code-error | | searchIcdOpd(['icd10'], …) | icd10/search-opd-error | | searchIcdIpd(['icd10'], …) | icd10/search-ipd-error | | searchIcdOpd(['icd9'], …) | icd9/search-opd-error | | searchIcdIpd(['icd9'], …) | icd9/search-ipd-error | | suggestBlockIcdOpd(['icd10'], …) | icd10/suggest-block-opd-error | | suggestBlockIcdIpd(['icd10'], …) | icd10/suggest-block-ipd-error | | suggestBlockIcdOpd(['icd9'], …) | icd9/suggest-block-opd-error | | suggestBlockIcdIpd(['icd9'], …) | icd9/suggest-block-ipd-error |

Chaining: ASR result → Diagnosis Code (ICD)

import { ASR, ICD, extractTranscript } from '@cariva/medical-sdk';

const asr = new ASR({ key, secret, mode: 'doc-ipd-soap' });
const icd = new ICD({ key, secret, baseUrl: 'https://…' });

const asrResult = await asr.process(audio);

// ICD-10 from the transcription
const { primary } = await icd.getIcdOpdCode(['icd10'], extractTranscript(asrResult));

// ICD-9 from the transcription
const { principal } = await icd.getIcdOpdCode(['icd9'], extractTranscript(asrResult));

// Combined ICD-10 + ICD-9 from a structured IPD record
const both = await icd.getIcdIpdCode(
  ['icd10', 'icd9'],
  [{ name: 'Assessment', content: asrResult.data.assessment ?? '' }]
);

Requirements

  • Node.js ≥ 22 — global fetch, File, Blob, stable WebCrypto
  • Browsers — ES2020+ with the Web Audio API
  • Bundlers — Vite, webpack, esbuild, Rollup, Next.js, or anything that reads exports

Migrating from the legacy SDKs

This package replaces @cariva/asr-sdk-browser and @cariva/asr-sdk-node with one package that runs on both.

npm uninstall @cariva/asr-sdk-browser @cariva/asr-sdk-node
npm install @cariva/medical-sdk
- import { ASR } from '@cariva/asr-sdk-browser';
+ import { ASR } from '@cariva/medical-sdk';

  const asr = new ASR({
    key, secret,
-   version: '1',
    mode: 'doc-ipd-soap',
  });

- const result = await asr.process('/path/to/audio.wav');
+ const bytes = await readFile('/path/to/audio.wav');
+ const result = await asr.process(new Blob([bytes], { type: 'audio/wav' }));

- await asr.cancelASRTask();
+ await asr.cancel();
- await asr.rateASRTask(5, 'good');
+ await asr.rate(5, 'good');

| What | Legacy asr-sdk-* | medical-sdk 1.0 | | ---------------- | ---------------------------------------- | --------------------------------- | | Package | one per platform | one for both | | Config version | required | removed | | Audio on Node | file path string, system lame binary | Blob / File, no binary needed | | UMD global | window.CarivaASRBrowser | window.CarivaMedicalSDK | | CDN path | /dist/index.umd.js | /dist/browser/index.umd.js |

Renamed:

| Legacy | Now | | -------------------- | ---------------------------- | | cancelASRTask() | cancel() | | rateASRTask() | rate() | | clearTask() | (removed — use cancel()) | | ASRError | CarivaError | | ASRValidationError | ValidationError | | ASRProcessingError | ProcessingError | | Mode | ASRMode | | MedicalSpecialty | ASRSpecialty | | TaskStatus | ASRTaskStatus | | TaskStatusResponse | ASRResult | | CreateTaskResponse | ASRCreateTaskResponse | | AsrResponseMap | ASRResponseMap | | MedClerkData | OPDClinicalRecordData |

Migrating from ICD10 to ICD

The old ICD10 class has been replaced by a unified ICD class that handles ICD-10, ICD-9, and combined ICD-9+ICD-10 through an optional modes array.

Minimal migration — just swap the class and add baseUrl; all calls work without passing modes because it defaults to ["icd10"]:

- import { ICD10 } from '@cariva/medical-sdk';
+ import { ICD } from '@cariva/medical-sdk';

- const icd = new ICD10({ key, secret });
+ const icd = new ICD({ key, secret, baseUrl: 'https://…' });

  // No other changes needed — modes defaults to ["icd10"]
- const { primary } = await icd.getOpdCode(note);
+ const { primary } = await icd.getIcdOpdCode(note);

Explicit modes — recommended when you want to be clear about which system is used or when you need ICD-9 or combined:

- const { primary } = await icd.getOpdCode(note);
+ const { primary } = await icd.getIcdOpdCode(['icd10'], note);

- const { primary } = await icd.getIpdCode(records);
+ const { principal } = await icd.getIcdIpdCode(['icd10'], records);

- const hits = await icd.searchOpd(query, { searchBy: 'text' });
+ const hits = await icd.searchIcdOpd(['icd10'], query, { searchBy: 'text' });

- const related = await icd.suggestBlockOpd(code);
+ const related = await icd.suggestBlockIcdOpd(['icd10'], code);

Authentication, the patientProfile option, and every payload shape for ICD-10 are unchanged. The new ICD-9 fields orStatus and drgStatus only appear when modes includes 'icd9'.

License

Apache-2.0