From 004f016cc0b6318f9b8d3345614a78235a961d41 Mon Sep 17 00:00:00 2001 From: shaselton-usds Date: Tue, 16 Jan 2024 10:16:23 -0800 Subject: [PATCH 1/7] moving json tests to version folders --- test/{ => 2.0}/json.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename test/{ => 2.0}/json.spec.ts (89%) diff --git a/test/json.spec.ts b/test/2.0/json.spec.ts similarity index 89% rename from test/json.spec.ts rename to test/2.0/json.spec.ts index ee1b05b..f155d39 100644 --- a/test/json.spec.ts +++ b/test/2.0/json.spec.ts @@ -1,6 +1,6 @@ import test from "ava" -import { loadFixtureStream } from "./utils" -import { validateJson } from "../src/json.js" +import { loadFixtureStream } from "../utils.js" +import { validateJson } from "../../src/json.js" test("validateJson", async (t) => { const result = await validateJson(loadFixtureStream("sample-1.json"), "v1.1") From adfbb843be2f3f63ec409a7516536dc96f4647f4 Mon Sep 17 00:00:00 2001 From: shaselton-usds Date: Tue, 16 Jan 2024 10:16:52 -0800 Subject: [PATCH 2/7] moving json tests to version folders --- test/1.1/json.spec.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/1.1/json.spec.ts diff --git a/test/1.1/json.spec.ts b/test/1.1/json.spec.ts new file mode 100644 index 0000000..f155d39 --- /dev/null +++ b/test/1.1/json.spec.ts @@ -0,0 +1,39 @@ +import test from "ava" +import { loadFixtureStream } from "../utils.js" +import { validateJson } from "../../src/json.js" + +test("validateJson", async (t) => { + const result = await validateJson(loadFixtureStream("sample-1.json"), "v1.1") + t.is(result.valid, false) + t.is(result.errors.length, 1) +}) + +test("validateJson empty", async (t) => { + const result = await validateJson( + loadFixtureStream("sample-empty.json"), + "v1.1" + ) + t.is(result.valid, false) + t.deepEqual(result.errors.length, 4) +}) + +test("validateJson maxErrors", async (t) => { + const result = await validateJson( + loadFixtureStream("sample-empty.json"), + "v1.1", + { + maxErrors: 1, + } + ) + t.is(result.valid, false) + t.deepEqual(result.errors.length, 1) +}) + +test("validateJson valid", async (t) => { + const result = await validateJson( + loadFixtureStream("sample-valid.json"), + "v1.1" + ) + t.is(result.valid, true) + t.deepEqual(result.errors.length, 0) +}) From d2a388e366ae6c11bed8924bbd4064262718f162 Mon Sep 17 00:00:00 2001 From: shaselton-usds Date: Tue, 23 Jan 2024 09:12:44 -0800 Subject: [PATCH 3/7] initial implementation for v2.0 in JSON --- src/json.ts | 3 + src/versions/2.0/csv.ts | 14 +- src/versions/2.0/json.ts | 125 +++++++++++++----- src/versions/2.0/types.ts | 6 +- test/2.0/json.spec.ts | 17 ++- test/fixtures/2.0/sample-valid.json | 198 ++++++++++++++++++++++++++++ 6 files changed, 314 insertions(+), 49 deletions(-) create mode 100644 test/fixtures/2.0/sample-valid.json diff --git a/src/json.ts b/src/json.ts index 8e78f7f..17a2b6c 100644 --- a/src/json.ts +++ b/src/json.ts @@ -4,6 +4,7 @@ import { JsonValidatorOptions, } from "./types.js" import { JsonValidatorOneOne } from "./versions/1.1/json.js" +import { JsonValidatorTwoZero } from "./versions/2.0/json.js" /** * @@ -18,6 +19,8 @@ export async function validateJson( ): Promise { if (version === "v1.1") { return JsonValidatorOneOne.validateJson(jsonInput, options) + } else if (version === "v2.0") { + return JsonValidatorTwoZero.validateJson(jsonInput, options) } return new Promise((resolve) => { resolve({ diff --git a/src/versions/2.0/csv.ts b/src/versions/2.0/csv.ts index d38047f..f2234b0 100644 --- a/src/versions/2.0/csv.ts +++ b/src/versions/2.0/csv.ts @@ -12,10 +12,10 @@ import { BillingCodeType, CHARGE_BILLING_CLASSES, CHARGE_SETTINGS, - CONTRACTING_METHODS, + STANDARD_CHARGE_METHODOLOGY, ChargeBillingClass, ChargeSetting, - ContractingMethod, + StandardChargeMethod, DRUG_UNITS, DrugUnit, } from "./types" @@ -354,7 +354,7 @@ export function validateWideFields( Object.entries(row).forEach(([field, value], columnIndex) => { if ( field.includes("contracting_method") && - !CONTRACTING_METHODS.includes(value as ContractingMethod) + !STANDARD_CHARGE_METHODOLOGY.includes(value as StandardChargeMethod) ) { errors.push( csvErr( @@ -364,7 +364,7 @@ export function validateWideFields( ERRORS.ALLOWED_VALUES( field, value, - CONTRACTING_METHODS as unknown as string[] + STANDARD_CHARGE_METHODOLOGY as unknown as string[] ) ) ) @@ -458,8 +458,8 @@ export function validateTallFields( } if ( - !CONTRACTING_METHODS.includes( - row["standard_charge | methodology"] as ContractingMethod + !STANDARD_CHARGE_METHODOLOGY.includes( + row["standard_charge | methodology"] as StandardChargeMethod ) ) { errors.push( @@ -471,7 +471,7 @@ export function validateTallFields( ERRORS.ALLOWED_VALUES( "standard_charge | methodology", row["standard_charge | methodology"], - CONTRACTING_METHODS as unknown as string[] + STANDARD_CHARGE_METHODOLOGY as unknown as string[] ) ) ) diff --git a/src/versions/2.0/json.ts b/src/versions/2.0/json.ts index ce0330b..91f128f 100644 --- a/src/versions/2.0/json.ts +++ b/src/versions/2.0/json.ts @@ -13,12 +13,12 @@ import { CHARGE_BILLING_CLASSES, CHARGE_SETTINGS, DRUG_UNITS, - CONTRACTING_METHODS, + STANDARD_CHARGE_METHODOLOGY, } from "./types.js" import { errorObjectToValidationError, parseJson } from "../common/json.js" const STANDARD_CHARGE_DEFINITIONS = { - billing_code_information: { + code_information: { type: "object", properties: { code: { type: "string" }, @@ -37,6 +37,7 @@ const STANDARD_CHARGE_DEFINITIONS = { }, required: ["unit", "type"], }, + standard_charges: { type: "object", properties: { @@ -48,11 +49,6 @@ const STANDARD_CHARGE_DEFINITIONS = { enum: CHARGE_SETTINGS, type: "string", }, - modifiers: { - type: "array", - items: { type: "string" }, - uniqueItems: true, - }, payers_information: { type: "array", items: { $ref: "#/definitions/payers_information" }, @@ -71,9 +67,9 @@ const STANDARD_CHARGE_DEFINITIONS = { properties: { description: { type: "string" }, drug_information: { $ref: "#/definitions/drug_information" }, - billing_code_information: { + code_information: { type: "array", - items: { $ref: "#/definitions/billing_code_information" }, + items: { $ref: "#/definitions/code_information" }, minItems: 1, }, standard_charges: { @@ -82,7 +78,7 @@ const STANDARD_CHARGE_DEFINITIONS = { minItems: 1, }, }, - required: ["description", "billing_code_information", "standard_charges"], + required: ["description", "code_information", "standard_charges"], }, payers_information: { type: "object", @@ -90,21 +86,16 @@ const STANDARD_CHARGE_DEFINITIONS = { payer_name: { type: "string" }, plan_name: { type: "string" }, additional_payer_notes: { type: "string" }, - standard_charge: { type: "number", exclusiveMinimum: 0 }, - standard_charge_percent: { type: "number", exclusiveMinimum: 0 }, - contracting_method: { - enum: CONTRACTING_METHODS, + standard_charge_dollar: { type: "number", exclusiveMinimum: 0 }, + standard_charge_algorithm: { type: "string" }, + standard_charge_percentage: { type: "number", exclusiveMinimum: 0 }, + estimated_amount: { type: "number", exclusiveMinimum: 0 }, + methodology: { + enum: STANDARD_CHARGE_METHODOLOGY, type: "string", }, }, - required: ["payer_name", "plan_name", "contracting_method"], - if: { - properties: { - contracting_method: { const: "percent of total billed charges" }, - }, - }, - then: { required: ["standard_charge_percent"] }, - else: { required: ["standard_charge"] }, + required: ["payer_name", "plan_name", "methodology"], }, } @@ -113,9 +104,9 @@ const STANDARD_CHARGE_PROPERTIES = { properties: { description: { type: "string" }, drug_information: { $ref: "#/definitions/drug_information" }, - billing_code_information: { + code_information: { type: "array", - items: { $ref: "#/definitions/billing_code_information" }, + items: { $ref: "#/definitions/code_information" }, minItems: 1, }, standard_charges: { @@ -124,7 +115,7 @@ const STANDARD_CHARGE_PROPERTIES = { minItems: 1, }, }, - required: ["description", "billing_code_information", "standard_charges"], + required: ["description", "code_information", "standard_charges"], } export const STANDARD_CHARGE_SCHEMA = { @@ -145,22 +136,92 @@ export const METADATA_DEFINITIONS = { }, required: ["license_number", "state"], }, + affirmation: { + type: "object", + properties: { + affirmation: { + const: + "To the best of its knowledge and belief, the hospital has included all applicable standard charge information in accordance with the requirements of 45 CFR 180.50, and the information encoded is true, accurate, and complete as of the date indicated.", + }, + confirm_affirmation: { + type: "boolean", + }, + }, + required: ["affirmation", "confirm_affirmation"], + }, + modifier_information: { + type: "object", + properties: { + description: { + type: "string", + }, + code: { + type: "string", + }, + modifier_payer_information: { + type: "array", + items: { + $ref: "#/definitions/modifier_payer_information", + }, + minItems: 1, + }, + }, + required: ["description", "modifier_payer_information", "code"], + }, + modifier_payer_information: { + type: "object", + properties: { + payer_name: { + type: "string", + }, + plan_name: { + type: "string", + }, + description: { + type: "string", + }, + }, + required: ["payer_name", "plan_name", "description"], + }, } export const METADATA_PROPERTIES = { hospital_name: { type: "string" }, last_updated_on: { type: "string", format: "date" }, license_information: { - type: "array", - items: { $ref: "#/definitions/license_information" }, - minItems: 1, + $ref: "#/definitions/license_information", }, version: { type: "string" }, - hospital_location: { type: "string" }, - financial_aid_policy: { type: "string" }, + hospital_address: { + type: "array", + items: { type: "string" }, + }, + hospital_location: { + type: "array", + items: { + type: "string", + }, + }, + affirmation: { + $ref: "#/definitions/affirmation", + }, + modifier_information: { + type: "array", + items: { + $ref: "#/definitions/modifier_information", + }, + }, } -export const METADATA_REQUIRED = ["hospital_name", "last_updated_on", "version"] +export const METADATA_REQUIRED = [ + "hospital_name", + "last_updated_on", + "hospital_location", + "hospital_address", + "license_information", + "version", + "affirmation", +] export const METADATA_SCHEMA = { $schema: "http://json-schema.org/draft-07/schema#", @@ -212,7 +273,7 @@ export async function validateJson( if (stack.length > 2 || key === "standard_charge_information") return if (typeof key === "string") { metadata[key] = value - } else { + } else { // is this where I need to put another check for the modifier information? hasCharges = true if (!validator.validate(STANDARD_CHARGE_SCHEMA, value)) { valid = false diff --git a/src/versions/2.0/types.ts b/src/versions/2.0/types.ts index 52056b5..4f25716 100644 --- a/src/versions/2.0/types.ts +++ b/src/versions/2.0/types.ts @@ -38,12 +38,12 @@ export const CHARGE_BILLING_CLASSES = [ type ChargeBillingClassTuple = typeof CHARGE_BILLING_CLASSES export type ChargeBillingClass = ChargeBillingClassTuple[number] -export const CONTRACTING_METHODS = [ +export const STANDARD_CHARGE_METHODOLOGY = [ "case rate", "fee schedule", "percent of total billed charges", "per diem", "other", ] as const -type ContractingMethodTuple = typeof CONTRACTING_METHODS -export type ContractingMethod = ContractingMethodTuple[number] +type StandardChargeTuple = typeof STANDARD_CHARGE_METHODOLOGY +export type StandardChargeMethod = StandardChargeTuple[number] diff --git a/test/2.0/json.spec.ts b/test/2.0/json.spec.ts index f155d39..3d84755 100644 --- a/test/2.0/json.spec.ts +++ b/test/2.0/json.spec.ts @@ -3,15 +3,17 @@ import { loadFixtureStream } from "../utils.js" import { validateJson } from "../../src/json.js" test("validateJson", async (t) => { - const result = await validateJson(loadFixtureStream("sample-1.json"), "v1.1") - t.is(result.valid, false) - t.is(result.errors.length, 1) + const result = await validateJson( + loadFixtureStream("/2.0/sample-valid.json"), + "v2.0" + ) + t.is(result.valid, true) }) - +/* test("validateJson empty", async (t) => { const result = await validateJson( loadFixtureStream("sample-empty.json"), - "v1.1" + "v2.0" ) t.is(result.valid, false) t.deepEqual(result.errors.length, 4) @@ -20,7 +22,7 @@ test("validateJson empty", async (t) => { test("validateJson maxErrors", async (t) => { const result = await validateJson( loadFixtureStream("sample-empty.json"), - "v1.1", + "v2.0", { maxErrors: 1, } @@ -32,8 +34,9 @@ test("validateJson maxErrors", async (t) => { test("validateJson valid", async (t) => { const result = await validateJson( loadFixtureStream("sample-valid.json"), - "v1.1" + "v2.0" ) t.is(result.valid, true) t.deepEqual(result.errors.length, 0) }) +*/ \ No newline at end of file diff --git a/test/fixtures/2.0/sample-valid.json b/test/fixtures/2.0/sample-valid.json new file mode 100644 index 0000000..2e95bee --- /dev/null +++ b/test/fixtures/2.0/sample-valid.json @@ -0,0 +1,198 @@ +{ + "hospital_name": "West Mercy Hospital", + "last_updated_on": "2024-07-01", + "version": "2.0.0", + "hospital_location": [ + "West Mercy Hospital", + "West Mercy Surgical Center" + ], + "hospital_address": [ + "12 Main Street, Fullerton, CA 92832", + "23 Ocean Ave, San Jose, CA 94088" + ], + "license_information": { + "license_number": "50056", + "state": "CA" + }, + "affirmation": { + "affirmation": "To the best of its knowledge and belief, the hospital has included all applicable standard charge information in accordance with the requirements of 45 CFR 180.50, and the information encoded is true, accurate, and complete as of the date indicated.", + "confirm_affirmation": true + }, + "standard_charge_information": [ + { + "description": "Major hip and knee joint replacement or reattachment of lower extremity without mcc", + "code_information": [ + { + "code": "470", + "type": "MS-DRG" + }, + { + "code": "175869", + "type": "LOCAL" + } + ], + "standard_charges": [ + { + "minimum": 20000, + "maximum": 20000, + "setting": "inpatient", + "payers_information": [ + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 20000, + "standard_charge_algorithm": "MS-DRG", + "estimated_amount": 22243.34, + "methodology": "case rate" + }, + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 20000, + "standard_charge_algorithm": "https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg", + "estimated_amount": 22243.34, + "methodology": "case rate" + }, + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 20000, + "standard_charge_algorithm": "The adjusted base payment rate indicated in the standard_charge|negotiated_dollar data element may be further adjusted for additional factors including transfers and outliers.", + "estimated_amount": 22243.34, + "methodology": "case rate" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_percentage": 50, + "estimated_amount": 23145.98, + "methodology": "percent of total billed charges" + } + ] + } + ] + }, + { + "description": "Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour", + "code_information": [ + { + "code": "92626", + "type": "CPT" + } + ], + "standard_charges": [ + { + "setting": "outpatient", + "gross_charge": 150, + "discounted_cash": 125, + "minimum": 98.98, + "maximum": 98.98, + "payers_information": [ + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 98.98, + "methodology": "fee schedule", + "additional_payer_notes": "110% of the Medicare fee schedule" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_percentage": 115, + "estimated_amount": 105.34, + "methodology": "fee schedule", + "additional_payer_notes": "115% of the state's workers' compensation amount" + } + ] + } + ] + }, + { + "description": "Behavioral health; residential (hospital residential treatment program), without room and board, per diem", + "code_information": [ + { + "code": "H0017", + "type": "HCPCS" + } + ], + "standard_charges": [ + { + "gross_charge": 2500, + "discounted_cash": 2250, + "minimum": 1200, + "maximum": 2000, + "setting": "inpatient", + "payers_information": [ + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 1500, + "methodology": "per diem" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_dollar": 2000, + "methodology": "per diem", + "additional_payer_notes": "per diem, days 1-3" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_dollar": 1800, + "methodology": "per diem", + "additional_payer_notes": "per diem, days 4-5" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_dollar": 1200, + "methodology": "per diem", + "additional_payer_notes": "per diem, days 6+" + } + ] + } + ] + }, + { + "description": "Treatment or observation room — observation room", + "code_information": [ + { + "code": "762", + "type": "RC" + } + ], + "standard_charges": [ + { + "gross_charge": 13000, + "discounted_cash": 12000, + "minimum": 8000, + "maximum": 10000, + "setting": "outpatient", + "payers_information": [ + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 8000, + "methodology": "case rate", + "additional_payer_notes": "Negotiated standard charge without surgery and without rule out myocardial infarction" + }, + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 10000, + "methodology": "case rate", + "additional_payer_notes": "Negotiated standard charge without surgery and with rule out myocardial infarction" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_dollar": 9000, + "methodology": "case rate" + } + ] + } + ] + } + ] +} \ No newline at end of file From 669ef1f6795b9093785219ead1098ee6c6cb8f9b Mon Sep 17 00:00:00 2001 From: shaselton-usds Date: Tue, 23 Jan 2024 09:21:42 -0800 Subject: [PATCH 4/7] updating json tests --- test/1.1/json.spec.ts | 8 ++++---- test/2.0/json.spec.ts | 10 +++++----- test/fixtures/{ => 1.1}/sample-1.json | 0 test/fixtures/{ => 1.1}/sample-empty.json | 0 test/fixtures/{ => 1.1}/sample-valid.json | 0 test/fixtures/2.0/sample-empty.json | 1 + 6 files changed, 10 insertions(+), 9 deletions(-) rename test/fixtures/{ => 1.1}/sample-1.json (100%) rename test/fixtures/{ => 1.1}/sample-empty.json (100%) rename test/fixtures/{ => 1.1}/sample-valid.json (100%) create mode 100644 test/fixtures/2.0/sample-empty.json diff --git a/test/1.1/json.spec.ts b/test/1.1/json.spec.ts index f155d39..e2dd79c 100644 --- a/test/1.1/json.spec.ts +++ b/test/1.1/json.spec.ts @@ -3,14 +3,14 @@ import { loadFixtureStream } from "../utils.js" import { validateJson } from "../../src/json.js" test("validateJson", async (t) => { - const result = await validateJson(loadFixtureStream("sample-1.json"), "v1.1") + const result = await validateJson(loadFixtureStream("/1.1/sample-1.json"), "v1.1") t.is(result.valid, false) t.is(result.errors.length, 1) }) test("validateJson empty", async (t) => { const result = await validateJson( - loadFixtureStream("sample-empty.json"), + loadFixtureStream("/1.1/sample-empty.json"), "v1.1" ) t.is(result.valid, false) @@ -19,7 +19,7 @@ test("validateJson empty", async (t) => { test("validateJson maxErrors", async (t) => { const result = await validateJson( - loadFixtureStream("sample-empty.json"), + loadFixtureStream("/1.1/sample-empty.json"), "v1.1", { maxErrors: 1, @@ -31,7 +31,7 @@ test("validateJson maxErrors", async (t) => { test("validateJson valid", async (t) => { const result = await validateJson( - loadFixtureStream("sample-valid.json"), + loadFixtureStream("/1.1/sample-valid.json"), "v1.1" ) t.is(result.valid, true) diff --git a/test/2.0/json.spec.ts b/test/2.0/json.spec.ts index 3d84755..b555a42 100644 --- a/test/2.0/json.spec.ts +++ b/test/2.0/json.spec.ts @@ -9,19 +9,19 @@ test("validateJson", async (t) => { ) t.is(result.valid, true) }) -/* + test("validateJson empty", async (t) => { const result = await validateJson( - loadFixtureStream("sample-empty.json"), + loadFixtureStream("/2.0/sample-empty.json"), "v2.0" ) t.is(result.valid, false) - t.deepEqual(result.errors.length, 4) + t.deepEqual(result.errors.length, 8) }) test("validateJson maxErrors", async (t) => { const result = await validateJson( - loadFixtureStream("sample-empty.json"), + loadFixtureStream("/2.0/sample-empty.json"), "v2.0", { maxErrors: 1, @@ -30,7 +30,7 @@ test("validateJson maxErrors", async (t) => { t.is(result.valid, false) t.deepEqual(result.errors.length, 1) }) - +/* test("validateJson valid", async (t) => { const result = await validateJson( loadFixtureStream("sample-valid.json"), diff --git a/test/fixtures/sample-1.json b/test/fixtures/1.1/sample-1.json similarity index 100% rename from test/fixtures/sample-1.json rename to test/fixtures/1.1/sample-1.json diff --git a/test/fixtures/sample-empty.json b/test/fixtures/1.1/sample-empty.json similarity index 100% rename from test/fixtures/sample-empty.json rename to test/fixtures/1.1/sample-empty.json diff --git a/test/fixtures/sample-valid.json b/test/fixtures/1.1/sample-valid.json similarity index 100% rename from test/fixtures/sample-valid.json rename to test/fixtures/1.1/sample-valid.json diff --git a/test/fixtures/2.0/sample-empty.json b/test/fixtures/2.0/sample-empty.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/test/fixtures/2.0/sample-empty.json @@ -0,0 +1 @@ +{} From 34e4d497d47f92715bf83c37a422cf932f0f1a04 Mon Sep 17 00:00:00 2001 From: shaselton-usds Date: Tue, 23 Jan 2024 09:41:48 -0800 Subject: [PATCH 5/7] additional tests around various errors of the file --- test/2.0/json.spec.ts | 32 ++++- test/fixtures/2.0/sample-errors.json | 196 +++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 7 deletions(-) create mode 100644 test/fixtures/2.0/sample-errors.json diff --git a/test/2.0/json.spec.ts b/test/2.0/json.spec.ts index b555a42..daf84cb 100644 --- a/test/2.0/json.spec.ts +++ b/test/2.0/json.spec.ts @@ -8,6 +8,7 @@ test("validateJson", async (t) => { "v2.0" ) t.is(result.valid, true) + t.deepEqual(result.errors.length, 0) }) test("validateJson empty", async (t) => { @@ -30,13 +31,30 @@ test("validateJson maxErrors", async (t) => { t.is(result.valid, false) t.deepEqual(result.errors.length, 1) }) -/* -test("validateJson valid", async (t) => { + +test("validateJson errorFile", async (t) => { const result = await validateJson( - loadFixtureStream("sample-valid.json"), + loadFixtureStream("/2.0/sample-errors.json"), "v2.0" ) - t.is(result.valid, true) - t.deepEqual(result.errors.length, 0) -}) -*/ \ No newline at end of file + t.is(result.valid, false) + t.deepEqual(result.errors.length, 3) + t.deepEqual(result.errors, + [ + { + path: '/standard_charges/0/payers_information/0/standard_charge_dollar', + field: 'standard_charge_dollar', + message: 'must be number' + }, + { + path: '/standard_charges/3/payers_information/2', + field: '2', + message: "must have required property 'methodology'" + }, + { + path: '/affirmation/affirmation', + field: 'affirmation', + message: 'must be equal to constant' + } + ]) +}) \ No newline at end of file diff --git a/test/fixtures/2.0/sample-errors.json b/test/fixtures/2.0/sample-errors.json new file mode 100644 index 0000000..a9fcb9f --- /dev/null +++ b/test/fixtures/2.0/sample-errors.json @@ -0,0 +1,196 @@ +{ + "hospital_name": "West Mercy Hospital", + "last_updated_on": "2024-07-01", + "version": "2.0.0", + "hospital_location": [ + "West Mercy Hospital", + "West Mercy Surgical Center" + ], + "hospital_address": [ + "12 Main Street, Fullerton, CA 92832", + "23 Ocean Ave, San Jose, CA 94088" + ], + "license_information": { + "license_number": "50056", + "state": "CA" + }, + "affirmation": { + "affirmation": "T the best of its knowledge and belief, the hospital has included all applicable standard charge information in accordance with the requirements of 45 CFR 180.50, and the information encoded is true, accurate, and complete as of the date indicated.", + "confirm_affirmation": true + }, + "standard_charge_information": [ + { + "description": "Major hip and knee joint replacement or reattachment of lower extremity without mcc", + "code_information": [ + { + "code": "470", + "type": "MS-DRG" + }, + { + "code": "175869", + "type": "LOCAL" + } + ], + "standard_charges": [ + { + "minimum": 20000, + "maximum": 20000, + "setting": "inpatient", + "payers_information": [ + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": "20000", + "standard_charge_algorithm": "MS-DRG", + "estimated_amount": 22243.34, + "methodology": "case rate" + }, + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 20000, + "standard_charge_algorithm": "https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg", + "estimated_amount": 22243.34, + "methodology": "case rate" + }, + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 20000, + "standard_charge_algorithm": "The adjusted base payment rate indicated in the standard_charge|negotiated_dollar data element may be further adjusted for additional factors including transfers and outliers.", + "estimated_amount": 22243.34, + "methodology": "case rate" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_percentage": 50, + "estimated_amount": 23145.98, + "methodology": "percent of total billed charges" + } + ] + } + ] + }, + { + "description": "Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour", + "code_information": [ + { + "code": "92626", + "type": "CPT" + } + ], + "standard_charges": [ + { + "setting": "outpatient", + "discounted_cash": 125, + "minimum": 98.98, + "maximum": 98.98, + "payers_information": [ + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 98.98, + "methodology": "fee schedule", + "additional_payer_notes": "110% of the Medicare fee schedule" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_percentage": 115, + "estimated_amount": 105.34, + "methodology": "fee schedule", + "additional_payer_notes": "115% of the state's workers' compensation amount" + } + ] + } + ] + }, + { + "description": "Behavioral health; residential (hospital residential treatment program), without room and board, per diem", + "code_information": [ + { + "code": "H0017", + "type": "HCPCS" + } + ], + "standard_charges": [ + { + "gross_charge": 2500, + "discounted_cash": 2250, + "minimum": 1200, + "maximum": 2000, + "setting": "inpatient", + "payers_information": [ + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 1500, + "methodology": "per diem" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_dollar": 2000, + "methodology": "per diem", + "additional_payer_notes": "per diem, days 1-3" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_dollar": 1800, + "methodology": "per diem", + "additional_payer_notes": "per diem, days 4-5" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_dollar": 1200, + "methodology": "per diem", + "additional_payer_notes": "per diem, days 6+" + } + ] + } + ] + }, + { + "description": "Treatment or observation room — observation room", + "code_information": [ + { + "code": "762", + "type": "RC" + } + ], + "standard_charges": [ + { + "gross_charge": 13000, + "discounted_cash": 12000, + "minimum": 8000, + "maximum": 10000, + "setting": "outpatient", + "payers_information": [ + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 8000, + "methodology": "case rate", + "additional_payer_notes": "Negotiated standard charge without surgery and without rule out myocardial infarction" + }, + { + "payer_name": "Platform Health Insurance", + "plan_name": "PPO", + "standard_charge_dollar": 10000, + "methodology": "case rate", + "additional_payer_notes": "Negotiated standard charge without surgery and with rule out myocardial infarction" + }, + { + "payer_name": "Region Health Insurance", + "plan_name": "HMO", + "standard_charge_dollar": 9000 + } + ] + } + ] + } + ] +} \ No newline at end of file From c215bf15cfb5ff67b87a60793f39873f8d301f9d Mon Sep 17 00:00:00 2001 From: shaselton-usds Date: Tue, 23 Jan 2024 09:43:22 -0800 Subject: [PATCH 6/7] prettier fix --- src/versions/2.0/json.ts | 5 +++-- test/1.1/json.spec.ts | 5 ++++- test/2.0/json.spec.ts | 37 ++++++++++++++++++------------------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/src/versions/2.0/json.ts b/src/versions/2.0/json.ts index 91f128f..1a68bd1 100644 --- a/src/versions/2.0/json.ts +++ b/src/versions/2.0/json.ts @@ -37,7 +37,7 @@ const STANDARD_CHARGE_DEFINITIONS = { }, required: ["unit", "type"], }, - + standard_charges: { type: "object", properties: { @@ -273,7 +273,8 @@ export async function validateJson( if (stack.length > 2 || key === "standard_charge_information") return if (typeof key === "string") { metadata[key] = value - } else { // is this where I need to put another check for the modifier information? + } else { + // is this where I need to put another check for the modifier information? hasCharges = true if (!validator.validate(STANDARD_CHARGE_SCHEMA, value)) { valid = false diff --git a/test/1.1/json.spec.ts b/test/1.1/json.spec.ts index e2dd79c..0ac43a2 100644 --- a/test/1.1/json.spec.ts +++ b/test/1.1/json.spec.ts @@ -3,7 +3,10 @@ import { loadFixtureStream } from "../utils.js" import { validateJson } from "../../src/json.js" test("validateJson", async (t) => { - const result = await validateJson(loadFixtureStream("/1.1/sample-1.json"), "v1.1") + const result = await validateJson( + loadFixtureStream("/1.1/sample-1.json"), + "v1.1" + ) t.is(result.valid, false) t.is(result.errors.length, 1) }) diff --git a/test/2.0/json.spec.ts b/test/2.0/json.spec.ts index daf84cb..9d18007 100644 --- a/test/2.0/json.spec.ts +++ b/test/2.0/json.spec.ts @@ -39,22 +39,21 @@ test("validateJson errorFile", async (t) => { ) t.is(result.valid, false) t.deepEqual(result.errors.length, 3) - t.deepEqual(result.errors, - [ - { - path: '/standard_charges/0/payers_information/0/standard_charge_dollar', - field: 'standard_charge_dollar', - message: 'must be number' - }, - { - path: '/standard_charges/3/payers_information/2', - field: '2', - message: "must have required property 'methodology'" - }, - { - path: '/affirmation/affirmation', - field: 'affirmation', - message: 'must be equal to constant' - } - ]) -}) \ No newline at end of file + t.deepEqual(result.errors, [ + { + path: "/standard_charges/0/payers_information/0/standard_charge_dollar", + field: "standard_charge_dollar", + message: "must be number", + }, + { + path: "/standard_charges/3/payers_information/2", + field: "2", + message: "must have required property 'methodology'", + }, + { + path: "/affirmation/affirmation", + field: "affirmation", + message: "must be equal to constant", + }, + ]) +}) From ec304e1ba8111812f24ff923bb132645f7e7340b Mon Sep 17 00:00:00 2001 From: shaselton-usds Date: Tue, 23 Jan 2024 11:40:32 -0800 Subject: [PATCH 7/7] adding stricter schema requirements to ensure attributes have values --- src/versions/2.0/json.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/versions/2.0/json.ts b/src/versions/2.0/json.ts index 1a68bd1..200dcac 100644 --- a/src/versions/2.0/json.ts +++ b/src/versions/2.0/json.ts @@ -21,7 +21,7 @@ const STANDARD_CHARGE_DEFINITIONS = { code_information: { type: "object", properties: { - code: { type: "string" }, + code: { type: "string", minLength: 1 }, type: { enum: BILLING_CODE_TYPES, type: "string", @@ -32,7 +32,7 @@ const STANDARD_CHARGE_DEFINITIONS = { drug_information: { type: "object", properties: { - unit: { type: "string" }, + unit: { type: "string", minLength: 1 }, type: { enum: DRUG_UNITS, type: "string" }, }, required: ["unit", "type"], @@ -65,7 +65,7 @@ const STANDARD_CHARGE_DEFINITIONS = { standard_charge_information: { type: "object", properties: { - description: { type: "string" }, + description: { type: "string", minLength: 1 }, drug_information: { $ref: "#/definitions/drug_information" }, code_information: { type: "array", @@ -83,8 +83,8 @@ const STANDARD_CHARGE_DEFINITIONS = { payers_information: { type: "object", properties: { - payer_name: { type: "string" }, - plan_name: { type: "string" }, + payer_name: { type: "string", minLength: 1 }, + plan_name: { type: "string", minLength: 1 }, additional_payer_notes: { type: "string" }, standard_charge_dollar: { type: "number", exclusiveMinimum: 0 }, standard_charge_algorithm: { type: "string" }, @@ -102,7 +102,7 @@ const STANDARD_CHARGE_DEFINITIONS = { const STANDARD_CHARGE_PROPERTIES = { type: "object", properties: { - description: { type: "string" }, + description: { type: "string", minLength: 1 }, drug_information: { $ref: "#/definitions/drug_information" }, code_information: { type: "array", @@ -134,7 +134,7 @@ export const METADATA_DEFINITIONS = { type: "string", }, }, - required: ["license_number", "state"], + required: ["state"], }, affirmation: { type: "object", @@ -154,9 +154,11 @@ export const METADATA_DEFINITIONS = { properties: { description: { type: "string", + minLength: 1, }, code: { type: "string", + minLength: 1, }, modifier_payer_information: { type: "array", @@ -173,12 +175,15 @@ export const METADATA_DEFINITIONS = { properties: { payer_name: { type: "string", + minLength: 1, }, plan_name: { type: "string", + minLength: 1, }, description: { type: "string", + minLength: 1, }, }, required: ["payer_name", "plan_name", "description"], @@ -186,21 +191,23 @@ export const METADATA_DEFINITIONS = { } export const METADATA_PROPERTIES = { - hospital_name: { type: "string" }, + hospital_name: { type: "string", minLength: 1 }, last_updated_on: { type: "string", format: "date" }, license_information: { $ref: "#/definitions/license_information", }, - version: { type: "string" }, + version: { type: "string", minLength: 1 }, hospital_address: { type: "array", items: { type: "string" }, + minItems: 1, }, hospital_location: { type: "array", items: { type: "string", }, + minItems: 1, }, affirmation: { $ref: "#/definitions/affirmation",