From 550536ac62ebb9ae9c280a30ef3fb58947bd3e50 Mon Sep 17 00:00:00 2001 From: Mint Thompson Date: Thu, 10 Oct 2024 13:40:27 -0400 Subject: [PATCH] Refactor validator classes The current state of this branch is a work-in-progress: lots of incomplete implementations and tests. Use this branch to work towards a complete implementation of the refactored validator classes. --- .gitignore | 3 +- .prettierrc | 2 +- jest.config.js | 17 + package-lock.json | 6446 +++++++++++------ package.json | 24 +- src/csv.ts | 193 +- src/errors/ValidationError.ts | 13 + src/errors/csv/AllowedValuesError.ts | 19 + src/errors/csv/AmbiguousFormatError.ts | 11 + src/errors/csv/ColumnMissingError.ts | 11 + src/errors/csv/CsvValidationError.ts | 22 + src/errors/csv/DuplicateColumnError.ts | 14 + src/errors/csv/DuplicateHeaderColumnError.ts | 14 + src/errors/csv/HeaderBlankError.ts | 11 + src/errors/csv/HeaderColumnMissingError.ts | 11 + src/errors/csv/InvalidDateError.ts | 16 + src/errors/csv/InvalidStateCodeError.ts | 11 + src/errors/csv/InvalidVersionError.ts | 7 + src/errors/csv/MinRowsError.ts | 7 + src/errors/csv/ProblemsInHeaderError.ts | 11 + src/errors/csv/RequiredValueError.ts | 16 + src/errors/csv/index.ts | 14 + src/errors/json/InvalidJsonError.ts | 10 + src/index.ts | 8 +- src/json.ts | 18 +- src/schemas/v2.0.0.json | 301 + src/schemas/v2.1.0.json | 388 + src/schemas/v2.2.0.json | 524 ++ src/types.ts | 65 +- src/utils.ts | 51 +- src/validators/BaseValidator.ts | 9 + src/validators/CsvValidator.ts | 538 ++ src/validators/JsonValidator.ts | 188 + src/validators/index.ts | 3 + src/versions/1.1/csv.ts | 572 -- src/versions/1.1/json.ts | 271 - src/versions/2.0/csv.ts | 1332 ---- src/versions/2.0/json.ts | 537 -- src/versions/common/csv.ts | 137 - src/versions/common/json.ts | 54 - test/1.1/csv.spec.ts | 295 - test/1.1/json.spec.ts | 42 - test/2.0/csv.e2e.spec.ts | 169 - test/2.0/csv.spec.ts | 1763 ----- test/2.0/json.spec.ts | 292 - test/common/csv.spec.ts | 17 - test/csv.e2e.spec.ts | 82 - test/filename.spec.ts | 23 - test/fixtures/1.1/sample-1.json | 66 - test/fixtures/1.1/sample-valid.json | 66 - test/fixtures/2.0/error.csv | 13 - test/fixtures/2.0/sample-2025-properties.json | 200 - .../sample-conditional-error-estimate.json | 195 - .../2.0/sample-conditional-error-minimum.json | 194 - .../2.0/sample-conditional-error-ndc.json | 195 - ...mple-conditional-error-standardcharge.json | 161 - .../2.0/sample-conditional-errors.json | 197 - .../2.0/sample-conditional-valid-minimum.json | 42 - test/fixtures/2.0/sample-empty.json | 1 - test/fixtures/2.0/sample-errors.json | 196 - .../fixtures/2.0/sample-tall-valid-quoted.csv | 16 - .../2.0/sample-wide-error-bad-enum.csv | 13 - ...ample-wide-error-header-standardcharge.csv | 13 - .../fixtures/2.0/sample-wide-error-header.csv | 13 - .../2.0/sample-wide-error-missing-value.csv | 13 - test/fixtures/2.0/sample-wide-error.csv | 13 - .../fixtures/2.0/sample-wide-header-empty.csv | 13 - .../2.0/sample-wide-missing-new-columns.csv | 13 - test/fixtures/sample-1.csv | 5 - test/fixtures/sample-2.csv | 4 - test/fixtures/{1.1 => }/sample-empty.json | 0 test/fixtures/{2.0 => }/sample-invalid.json | 0 test/fixtures/{2.0 => }/sample-tall-valid.csv | 32 +- test/fixtures/{2.0 => }/sample-valid-bom.json | 0 test/fixtures/{2.0 => }/sample-valid.json | 0 test/fixtures/{2.0 => }/sample-wide-valid.csv | 26 +- test/testhelpers/createFixtureStream.ts | 9 + test/tsconfig.json | 9 + test/utils.ts | 11 - test/validators/CsvValidator.test.ts | 653 ++ test/validators/JsonValidator.test.ts | 179 + tsconfig.json | 2 +- 82 files changed, 7544 insertions(+), 9601 deletions(-) create mode 100644 jest.config.js create mode 100644 src/errors/ValidationError.ts create mode 100644 src/errors/csv/AllowedValuesError.ts create mode 100644 src/errors/csv/AmbiguousFormatError.ts create mode 100644 src/errors/csv/ColumnMissingError.ts create mode 100644 src/errors/csv/CsvValidationError.ts create mode 100644 src/errors/csv/DuplicateColumnError.ts create mode 100644 src/errors/csv/DuplicateHeaderColumnError.ts create mode 100644 src/errors/csv/HeaderBlankError.ts create mode 100644 src/errors/csv/HeaderColumnMissingError.ts create mode 100644 src/errors/csv/InvalidDateError.ts create mode 100644 src/errors/csv/InvalidStateCodeError.ts create mode 100644 src/errors/csv/InvalidVersionError.ts create mode 100644 src/errors/csv/MinRowsError.ts create mode 100644 src/errors/csv/ProblemsInHeaderError.ts create mode 100644 src/errors/csv/RequiredValueError.ts create mode 100644 src/errors/csv/index.ts create mode 100644 src/errors/json/InvalidJsonError.ts create mode 100644 src/schemas/v2.0.0.json create mode 100644 src/schemas/v2.1.0.json create mode 100644 src/schemas/v2.2.0.json create mode 100644 src/validators/BaseValidator.ts create mode 100644 src/validators/CsvValidator.ts create mode 100644 src/validators/JsonValidator.ts create mode 100644 src/validators/index.ts delete mode 100644 src/versions/1.1/csv.ts delete mode 100644 src/versions/1.1/json.ts delete mode 100644 src/versions/2.0/csv.ts delete mode 100644 src/versions/2.0/json.ts delete mode 100644 src/versions/common/csv.ts delete mode 100644 src/versions/common/json.ts delete mode 100644 test/1.1/csv.spec.ts delete mode 100644 test/1.1/json.spec.ts delete mode 100644 test/2.0/csv.e2e.spec.ts delete mode 100644 test/2.0/csv.spec.ts delete mode 100644 test/2.0/json.spec.ts delete mode 100644 test/common/csv.spec.ts delete mode 100644 test/csv.e2e.spec.ts delete mode 100644 test/filename.spec.ts delete mode 100644 test/fixtures/1.1/sample-1.json delete mode 100644 test/fixtures/1.1/sample-valid.json delete mode 100644 test/fixtures/2.0/error.csv delete mode 100644 test/fixtures/2.0/sample-2025-properties.json delete mode 100644 test/fixtures/2.0/sample-conditional-error-estimate.json delete mode 100644 test/fixtures/2.0/sample-conditional-error-minimum.json delete mode 100644 test/fixtures/2.0/sample-conditional-error-ndc.json delete mode 100644 test/fixtures/2.0/sample-conditional-error-standardcharge.json delete mode 100644 test/fixtures/2.0/sample-conditional-errors.json delete mode 100644 test/fixtures/2.0/sample-conditional-valid-minimum.json delete mode 100644 test/fixtures/2.0/sample-empty.json delete mode 100644 test/fixtures/2.0/sample-errors.json delete mode 100644 test/fixtures/2.0/sample-tall-valid-quoted.csv delete mode 100644 test/fixtures/2.0/sample-wide-error-bad-enum.csv delete mode 100644 test/fixtures/2.0/sample-wide-error-header-standardcharge.csv delete mode 100644 test/fixtures/2.0/sample-wide-error-header.csv delete mode 100644 test/fixtures/2.0/sample-wide-error-missing-value.csv delete mode 100644 test/fixtures/2.0/sample-wide-error.csv delete mode 100644 test/fixtures/2.0/sample-wide-header-empty.csv delete mode 100644 test/fixtures/2.0/sample-wide-missing-new-columns.csv delete mode 100644 test/fixtures/sample-1.csv delete mode 100644 test/fixtures/sample-2.csv rename test/fixtures/{1.1 => }/sample-empty.json (100%) rename test/fixtures/{2.0 => }/sample-invalid.json (100%) rename test/fixtures/{2.0 => }/sample-tall-valid.csv (99%) rename test/fixtures/{2.0 => }/sample-valid-bom.json (100%) rename test/fixtures/{2.0 => }/sample-valid.json (100%) rename test/fixtures/{2.0 => }/sample-wide-valid.csv (99%) create mode 100644 test/testhelpers/createFixtureStream.ts create mode 100644 test/tsconfig.json delete mode 100644 test/utils.ts create mode 100644 test/validators/CsvValidator.test.ts create mode 100644 test/validators/JsonValidator.test.ts diff --git a/.gitignore b/.gitignore index 5e069c0..435ba01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ lib/ +coverage/ .DS_Store -.vscode +.vscode \ No newline at end of file diff --git a/.prettierrc b/.prettierrc index 48e90e8..29fd18b 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,6 +1,6 @@ { "endOfLine": "lf", - "semi": false, + "semi": true, "singleQuote": false, "tabWidth": 2, "trailingComma": "es5" diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..5709969 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,17 @@ +const config = { + moduleFileExtensions: ['js', 'ts'], + transform: { + '^.+\\.(js|jsx|ts|tsx)$': [ + 'ts-jest', + { + tsconfig: '/test/tsconfig.json' + } + ] + }, + testMatch: ['**/test/**/*.test.(ts|js)'], + testEnvironment: 'node', + setupFilesAfterEnv: ['jest-extended/all'], + preset: 'ts-jest' +} + +export default config \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c52b69b..8a3d2f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,16 +14,22 @@ "@types/papaparse": "^5.3.14", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", + "lodash": "^4.17.21", "papaparse": "^5.4.1" }, "devDependencies": { + "@types/jest": "^29.5.13", + "@types/lodash": "^4.17.7", "@typescript-eslint/eslint-plugin": "^8.5.0", "@typescript-eslint/parser": "^8.5.0", - "ava": "^6.1.3", + "copyfiles": "^2.4.1", "eslint": "^9.10.0", "eslint-config-prettier": "^9.1.0", "esm": "^3.2.25", + "jest": "^29.7.0", + "jest-extended": "^4.0.2", "prettier": "^3.3.3", + "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "typescript": "^5.6.0" } @@ -37,1506 +43,3757 @@ "node": ">=0.10.0" } }, - "node_modules/@ava/typescript": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@ava/typescript/-/typescript-5.0.0.tgz", - "integrity": "sha512-2twsQz2fUd95QK1MtKuEnjkiN47SKHZfi/vWj040EN6Eo2ZW3SNcAwncJqXXoMTYZTWtBRXYp3Fg8z+JkFI9aQ==", + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "escape-string-regexp": "^5.0.0", - "execa": "^8.0.1" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { - "node": "^18.18 || ^20.8 || ^21 || ^22" + "node": ">=6.0.0" } }, - "node_modules/@ava/typescript/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" }, "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "node": ">=6.9.0" } }, - "node_modules/@ava/typescript/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "node_modules/@babel/compat-data": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true, "engines": { - "node": ">=16" + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@ava/typescript/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "node": ">=16.17.0" + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/@ava/typescript/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/@babel/generator": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dev": true, - "license": "ISC", - "optional": true, - "peer": true, - "engines": { - "node": ">=14" + "dependencies": { + "@babel/types": "^7.25.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=6.9.0" }, "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "@babel/core": "^7.0.0" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", - "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dev": true, "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "color-convert": "^1.9.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": ">=4" } }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "node_modules/@eslint/js": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", - "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=0.8.0" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=4" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", - "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "dependencies": { - "levn": "^0.4.1" + "has-flag": "^3.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=4" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@babel/parser": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", "dev": true, - "engines": { - "node": ">=12.22" + "dependencies": { + "@babel/types": "^7.25.6" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, - "engines": { - "node": ">=18.18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, - "license": "BSD-3-Clause", "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" + "@babel/helper-plugin-utils": "^7.12.13" }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", + "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, "engines": { - "node": ">= 8" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "@babel/helper-plugin-utils": "^7.10.4" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@rollup/pluginutils": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", - "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, - "license": "MIT", "dependencies": { - "estree-walker": "^2.0.1", - "picomatch": "^2.2.2" + "@babel/helper-plugin-utils": "^7.8.0" }, - "engines": { - "node": ">= 8.0.0" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", "dev": true, - "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@streamparser/json": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.21.tgz", - "integrity": "sha512-v+49JBiG1kmc/9Ug79Lz9wyKaRocBgCnpRaLpdy7p0d3ICKtOAfc/H/Epa1j3F6YdnzjnZKKrnJ8xnh/v1P8Aw==", - "license": "MIT" + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "node_modules/@types/node": { - "version": "20.16.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz", - "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==", - "license": "MIT", + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, "dependencies": { - "undici-types": "~6.19.2" + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@types/papaparse": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.14.tgz", - "integrity": "sha512-LxJ4iEFcpqc6METwp9f6BV6VVc43m6MfH0VqFosHvrUgfXiFe6ww7R3itkOQ+TCK6Y+Iv/+RnnvtRZnkc5Kc9g==", - "license": "MIT", + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, "dependencies": { - "@types/node": "*" + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz", - "integrity": "sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==", + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.5.0", - "@typescript-eslint/type-utils": "8.5.0", - "@typescript-eslint/utils": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "@babel/helper-plugin-utils": "^7.14.5" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", + "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz", - "integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==", + "node_modules/@babel/traverse": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.5.0", - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/typescript-estree": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0", - "debug": "^4.3.4" + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", + "debug": "^4.3.1", + "globals": "^11.1.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", - "integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==", + "node_modules/@eslint-community/regexpp": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.18.0.tgz", + "integrity": "sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0" + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz", - "integrity": "sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==", + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "8.5.0", - "@typescript-eslint/utils": "8.5.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz", - "integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==", + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/js": { + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.10.0.tgz", + "integrity": "sha512-fuXtbiP5GWIn8Fz+LWoOMVf/Jxm+aajZYkhi6CuEm4SxymFM+eUWzbO9qXT+L0iCkL5+KGYMCSGxo686H19S1g==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.1.0.tgz", + "integrity": "sha512-autAXT203ixhqei9xt+qkYOvY8l6LAFIdT2UXc/RPNeUVfqRF1BV94GTJyVPFKT8nFM6MyVJhjLj9E8JWvf5zQ==", + "dev": true, + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@streamparser/json": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.21.tgz", + "integrity": "sha512-v+49JBiG1kmc/9Ug79Lz9wyKaRocBgCnpRaLpdy7p0d3ICKtOAfc/H/Epa1j3F6YdnzjnZKKrnJ8xnh/v1P8Aw==", + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.13", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz", + "integrity": "sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", + "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.16.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz", + "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/papaparse": { + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.14.tgz", + "integrity": "sha512-LxJ4iEFcpqc6METwp9f6BV6VVc43m6MfH0VqFosHvrUgfXiFe6ww7R3itkOQ+TCK6Y+Iv/+RnnvtRZnkc5Kc9g==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.5.0.tgz", + "integrity": "sha512-lHS5hvz33iUFQKuPFGheAB84LwcJ60G8vKnEhnfcK1l8kGVLro2SFYW6K0/tj8FUhRJ0VHyg1oAfg50QGbPPHw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.5.0", + "@typescript-eslint/type-utils": "8.5.0", + "@typescript-eslint/utils": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.5.0.tgz", + "integrity": "sha512-gF77eNv0Xz2UJg/NbpWJ0kqAm35UMsvZf1GHj8D9MRFTj/V3tAciIWXfmPLsAAF/vUlpWPvUDyH1jjsr0cMVWw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.5.0", + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/typescript-estree": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.5.0.tgz", + "integrity": "sha512-06JOQ9Qgj33yvBEx6tpC8ecP9o860rsR22hWMEd12WcTRrfaFgHr2RB/CA/B+7BMhHkXT4chg2MyboGdFGawYg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.5.0.tgz", + "integrity": "sha512-N1K8Ix+lUM+cIDhL2uekVn/ZD7TZW+9/rwz8DclQpcQ9rk4sIL5CAlBC0CugWKREmDjBzI/kQqU4wkg46jWLYA==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.5.0", + "@typescript-eslint/utils": "8.5.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.5.0.tgz", + "integrity": "sha512-qjkormnQS5wF9pjSi6q60bKUHH44j2APxfh9TQRXK8wbYVeDYYdYJGIROL87LGZZ2gz3Rbmjc736qyL8deVtdw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz", + "integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/visitor-keys": "8.5.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz", + "integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.5.0", + "@typescript-eslint/types": "8.5.0", + "@typescript-eslint/typescript-estree": "8.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz", + "integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.5.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001662", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz", + "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/copyfiles": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", + "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", + "dev": true, + "dependencies": { + "glob": "^7.0.5", + "minimatch": "^3.0.3", + "mkdirp": "^1.0.4", + "noms": "0.0.0", + "through2": "^2.0.1", + "untildify": "^4.0.0", + "yargs": "^16.1.0" + }, + "bin": { + "copyfiles": "copyfiles", + "copyup": "copyfiles" + } + }, + "node_modules/copyfiles/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/copyfiles/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/copyfiles/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/copyfiles/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/copyfiles/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/copyfiles/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/create-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.26", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.26.tgz", + "integrity": "sha512-Z+OMe9M/V6Ep9n/52+b7lkvYEps26z4Yz3vjWL1V61W0q+VLF1pOHhMY17sa4roz4AWmULSI8E6SAojZA5L0YQ==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint": { + "version": "9.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", + "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.18.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.10.0", + "@eslint/plugin-kit": "^0.1.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/espree": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, + "dependencies": { + "acorn": "^8.12.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.5.0.tgz", - "integrity": "sha512-vEG2Sf9P8BPQ+d0pxdfndw3xIXaoSjliG0/Ejk7UggByZPKXmJmw3GW5jV2gHNQNawBUyfahoSiCFVov0Ruf7Q==", + "node_modules/execa/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/visitor-keys": "8.5.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/execa/node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=6" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/execa/node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "path-key": "^3.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=8" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.5.0.tgz", - "integrity": "sha512-6yyGYVL0e+VzGYp60wvkBHiqDWOpT63pdMV2CVG4LVDd5uR6q1qQN/7LafBZtAtNIn/mqXjsSeS5ggv/P0iECw==", + "node_modules/execa/node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.5.0", - "@typescript-eslint/types": "8.5.0", - "@typescript-eslint/typescript-estree": "8.5.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.5.0.tgz", - "integrity": "sha512-yTPqMnbAZJNy2Xq2XU8AdtOW9tJIr+UQb64aXB9f3B1498Zx9JorVgFJcZpEc9UBuCCrdzKID2RGAMkYcDtZOw==", + "node_modules/execa/node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, - "dependencies": { - "@typescript-eslint/types": "8.5.0", - "eslint-visitor-keys": "^3.4.3" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=6" } }, - "node_modules/@vercel/nft": { - "version": "0.26.5", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.26.5.tgz", - "integrity": "sha512-NHxohEqad6Ra/r4lGknO52uc/GrWILXAMs1BB4401GTqww0fw1bAqzpG1XHuDO+dprg4GvsD9ZLLSsdo78p9hQ==", + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.5", - "@rollup/pluginutils": "^4.0.0", - "acorn": "^8.6.0", - "acorn-import-attributes": "^1.9.2", - "async-sema": "^3.1.1", - "bindings": "^1.4.0", - "estree-walker": "2.0.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.2", - "node-gyp-build": "^4.2.2", - "resolve-from": "^5.0.0" - }, - "bin": { - "nft": "out/cli.js" - }, "engines": { - "node": ">=16" + "node": ">= 0.8.0" } }, - "node_modules/@vercel/nft/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, - "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, - "license": "ISC" + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } }, - "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "is-glob": "^4.0.1" }, "engines": { - "node": ">=0.4.0" + "node": ">= 6" } }, - "node_modules/acorn-import-attributes": { - "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8" + "dependencies": { + "reusify": "^1.0.4" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "dependencies": { + "bser": "2.1.1" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, - "license": "MIT", "dependencies": { - "acorn": "^8.11.0" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">=0.4.0" + "node": ">=16.0.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dev": true, - "license": "MIT", "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" + "minimatch": "^5.0.1" } }, - "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "balanced-match": "^1.0.0" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "license": "MIT", + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" + "brace-expansion": "^2.0.1" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "engines": { + "node": ">=10" } }, - "node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, - "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, - "license": "ISC", "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": ">=10" + "node": ">=16" } }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=0.10.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/arrgv": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arrgv/-/arrgv-1.0.2.tgz", - "integrity": "sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "engines": { - "node": ">=8.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/arrify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", - "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.9.0" } }, - "node_modules/async-sema": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", - "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, - "license": "MIT" + "engines": { + "node": "6.* || 8.* || >= 10.*" + } }, - "node_modules/ava": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/ava/-/ava-6.1.3.tgz", - "integrity": "sha512-tkKbpF1pIiC+q09wNU9OfyTDYZa8yuWvU2up3+lFJ3lr1RmnYh2GBpPwzYUEB0wvTPIUysGjcZLNZr7STDviRA==", + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, - "license": "MIT", - "dependencies": { - "@vercel/nft": "^0.26.2", - "acorn": "^8.11.3", - "acorn-walk": "^8.3.2", - "ansi-styles": "^6.2.1", - "arrgv": "^1.0.2", - "arrify": "^3.0.0", - "callsites": "^4.1.0", - "cbor": "^9.0.1", - "chalk": "^5.3.0", - "chunkd": "^2.0.1", - "ci-info": "^4.0.0", - "ci-parallel-vars": "^1.0.1", - "cli-truncate": "^4.0.0", - "code-excerpt": "^4.0.0", - "common-path-prefix": "^3.0.0", - "concordance": "^5.0.4", - "currently-unhandled": "^0.4.1", - "debug": "^4.3.4", - "emittery": "^1.0.1", - "figures": "^6.0.1", - "globby": "^14.0.0", - "ignore-by-default": "^2.1.0", - "indent-string": "^5.0.0", - "is-plain-object": "^5.0.0", - "is-promise": "^4.0.0", - "matcher": "^5.0.0", - "memoize": "^10.0.0", - "ms": "^2.1.3", - "p-map": "^7.0.1", - "package-config": "^5.0.0", - "picomatch": "^3.0.1", - "plur": "^5.1.0", - "pretty-ms": "^9.0.0", - "resolve-cwd": "^3.0.0", - "stack-utils": "^2.0.6", - "strip-ansi": "^7.1.0", - "supertap": "^3.0.1", - "temp-dir": "^3.0.0", - "write-file-atomic": "^5.0.1", - "yargs": "^17.7.2" - }, - "bin": { - "ava": "entrypoints/cli.mjs" - }, "engines": { - "node": "^18.18 || ^20.8 || ^21 || ^22" - }, - "peerDependencies": { - "@ava/typescript": "*" - }, - "peerDependenciesMeta": { - "@ava/typescript": { - "optional": true - } + "node": ">=8.0.0" } }, - "node_modules/ava/node_modules/callsites": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.2.0.tgz", - "integrity": "sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==", + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "license": "MIT", "engines": { - "node": ">=12.20" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ava/node_modules/globby": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", - "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, - "license": "MIT", "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.2", - "ignore": "^5.2.4", - "path-type": "^5.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.1.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { - "node": ">=18" + "node": "*" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/ava/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/ava/node_modules/path-type": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", - "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "is-glob": "^4.0.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=10.13.0" } }, - "node_modules/ava/node_modules/picomatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-3.0.1.tgz", - "integrity": "sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag==", + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "license": "MIT", "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ava/node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "license": "MIT", "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/ava/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, - "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "function-bind": "^1.1.2" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">= 0.4" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" + "engines": { + "node": ">=10.17.0" } }, - "node_modules/blueimp-md5": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", - "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", - "dev": true + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, - "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "engines": { - "node": ">=6" + "node": ">=0.8.19" } }, - "node_modules/cbor": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-9.0.2.tgz", - "integrity": "sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==", + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, - "license": "MIT", "dependencies": { - "nofilter": "^3.1.0" - }, - "engines": { - "node": ">=16" + "once": "^1.3.0", + "wrappy": "1" } }, - "node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, - "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, - "license": "ISC", "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/chunkd": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/chunkd/-/chunkd-2.0.1.tgz", - "integrity": "sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==", - "dev": true - }, - "node_modules/ci-info": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.0.0.tgz", - "integrity": "sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==", + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/ci-parallel-vars": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ci-parallel-vars/-/ci-parallel-vars-1.0.1.tgz", - "integrity": "sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==", - "dev": true - }, - "node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, - "license": "MIT", "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, "engines": { - "node": ">=12" + "node": ">=8" } }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "dev": true }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "engines": { "node": ">=8" } }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/code-excerpt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", - "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "dependencies": { - "convert-to-spaces": "^2.0.1" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=10" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/istanbul-lib-report/node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "semver": "^7.5.3" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concordance": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", - "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "dependencies": { - "date-time": "^3.1.0", - "esutils": "^2.0.3", - "fast-diff": "^1.2.0", - "js-string-escape": "^1.0.1", - "lodash": "^4.17.15", - "md5-hex": "^3.0.1", - "semver": "^7.3.2", - "well-known-symbols": "^2.0.0" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" }, "engines": { - "node": ">=10.18.0 <11 || >=12.14.0 <13 || >=14" + "node": ">=10" } }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/convert-to-spaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", - "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=8" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", "dev": true, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" }, "engines": { - "node": ">= 8" + "node": ">=10" } }, - "node_modules/currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "array-find-index": "^1.0.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/date-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", - "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "time-zone": "^1.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "dependencies": { - "ms": "2.1.2" + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">=6.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { - "supports-color": { + "node-notifier": { "optional": true } } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, - "license": "Apache-2.0", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/emittery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-1.0.1.tgz", - "integrity": "sha512-2ID6FdrMD9KDLldGesP6317G78K7km/kMcwItRtVFva7I/cSEOIaLpewaUb+YLXVwdAp3Ctfxh/V5zIl1sj7dQ==", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=14.16" + "node": ">=8" }, "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=12" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint": { - "version": "9.10.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.10.0.tgz", - "integrity": "sha512-Y4D0IgtBZfOcOUAIQTSXBKoNGfY0REGqHJG6+Q81vNippW5YlKjHFj4soMxamKK1NXHUWuBZTLdU3Km+L/pcHw==", + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.18.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.10.0", - "@eslint/plugin-kit": "^0.1.0", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.12.4", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.2", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" }, "bin": { - "eslint": "bin/eslint.js" + "jest": "bin/jest.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, "peerDependencies": { - "jiti": "*" + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "peerDependenciesMeta": { - "jiti": { + "node-notifier": { "optional": true } } }, - "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", - "dev": true, - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", - "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=8" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/eslint/node_modules/ansi-styles": { + "node_modules/jest-config/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -1551,7 +3808,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/chalk": { + "node_modules/jest-config/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -1567,695 +3824,834 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/jest-config/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/esm": { - "version": "3.2.25", - "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", - "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, "engines": { - "node": ">=6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "dependencies": { - "acorn": "^8.12.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=8" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "estraverse": "^5.1.0" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=0.10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/jest-extended": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-4.0.2.tgz", + "integrity": "sha512-FH7aaPgtGYHc9mRjriS0ZEHYM5/W69tLrFTIdzm+yJgeoCmmrSB/luSfMSqWP9O29QWHPEmJ4qmU6EwsZideog==", "dev": true, "dependencies": { - "estraverse": "^5.2.0" + "jest-diff": "^29.0.0", + "jest-get-type": "^29.0.0" }, "engines": { - "node": ">=4.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "jest": ">=27.2.5" + }, + "peerDependenciesMeta": { + "jest": { + "optional": true + } } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", "dev": true, "engines": { - "node": ">=4.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">=8.6.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "dependencies": { - "is-glob": "^4.0.1" + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, "engines": { - "node": ">= 6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fast-uri": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "reusify": "^1.0.4" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/figures": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { - "is-unicode-supported": "^2.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { - "flat-cache": "^4.0.0" + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" }, "engines": { - "node": ">=16.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "color-convert": "^2.0.1" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/find-up-simple": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", - "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, - "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, "engines": { - "node": ">=18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" }, "engines": { - "node": ">=16" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "dev": true - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, - "license": "ISC", "dependencies": { - "minipass": "^3.0.0" + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" }, "engines": { - "node": ">= 8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "color-convert": "^2.0.1" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "ISC", "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/gauge/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/gauge/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, - "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/gauge/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "color-convert": "^2.0.1" }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", - "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/jest-runner/node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, "engines": { - "node": "*" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "dependencies": { - "is-glob": "^4.0.3" + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" }, "engines": { - "node": ">=10.13.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=18" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, "engines": { - "node": ">= 4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/ignore-by-default": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-2.1.0.tgz", - "integrity": "sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==", + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=10 <11 || >=12 <13 || >=14" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, "engines": { - "node": ">=0.8.19" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/indent-string": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=12" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/irregular-plurals": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", - "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", + "node_modules/jest-util/node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "engines": { "node": ">=8" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, "engines": { - "node": ">=0.10.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">=12" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "dependencies": { - "is-extglob": "^2.1.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, - "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, "engines": { - "node": ">=0.12.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "dev": true - }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "node_modules/jest-watcher/node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, - "optional": true, - "peer": true, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/emittery?sponsor=1" } }, - "node_modules/is-unicode-supported": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/js-string-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2268,12 +4664,30 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2285,6 +4699,18 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -2294,6 +4720,24 @@ "json-buffer": "3.0.1" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2307,18 +4751,11 @@ "node": ">= 0.8.0" } }, - "node_modules/load-json-file": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-7.0.1.tgz", - "integrity": "sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true }, "node_modules/locate-path": { "version": "6.0.0", @@ -2339,6 +4776,12 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, "node_modules/lodash.merge": { @@ -2347,31 +4790,20 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "license": "MIT", "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "yallist": "^3.0.2" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } + "node_modules/lru-cache/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true }, "node_modules/make-error": { "version": "1.3.6", @@ -2379,56 +4811,20 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, - "node_modules/matcher": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-5.0.0.tgz", - "integrity": "sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/md5-hex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", - "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", - "dev": true, - "dependencies": { - "blueimp-md5": "^2.10.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/memoize": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.0.0.tgz", - "integrity": "sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA==", + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, - "license": "MIT", "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/memoize?sponsor=1" + "tmpl": "1.0.5" } }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "optional": true, - "peer": true + "dev": true }, "node_modules/merge2": { "version": "1.4.1", @@ -2453,33 +4849,6 @@ "node": ">=8.6" } }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2492,43 +4861,6 @@ "node": "*" } }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -2554,116 +4886,51 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", - "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", - "dev": true, - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true }, - "node_modules/nofilter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", - "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.19" - } + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "node_modules/noms": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", + "integrity": "sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==", "dev": true, - "license": "ISC", "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" + "inherits": "^2.0.1", + "readable-stream": "~1.0.31" } }, - "node_modules/npm-run-path": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", - "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "node_modules/noms/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "dev": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } + "node_modules/noms/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2677,23 +4944,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -2741,34 +4991,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.2.tgz", - "integrity": "sha512-z4cYYMMdKHzw4O5UkWJImbZynVIo0lSGTXc7bzB1e/rrDqkgGUNysK/o4bTr+0+xKvvLoTyGqYC4Fgljy9qe1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-config": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/package-config/-/package-config-5.0.0.tgz", - "integrity": "sha512-GYTTew2slBcYdvRHqjhwaaydVMvn/qrGC323+nKclYioNSLTDUM/lGgtGTgyHVtYcozb+XkE8CNhwcraOmZ9Mg==", + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, - "license": "MIT", - "dependencies": { - "find-up-simple": "^1.0.0", - "load-json-file": "^7.0.1" - }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, "node_modules/papaparse": { @@ -2788,14 +5017,19 @@ "node": ">=6" } }, - "node_modules/parse-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, - "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, "engines": { - "node": ">=18" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -2828,6 +5062,18 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2840,21 +5086,79 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/plur": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", - "integrity": "sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==", + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "dependencies": { - "irregular-plurals": "^3.3.0" + "p-try": "^2.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2879,20 +5183,49 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/pretty-ms": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.1.0.tgz", - "integrity": "sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==", + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, - "license": "MIT", "dependencies": { - "parse-ms": "^4.0.0" + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" }, "engines": { - "node": ">=18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" } }, "node_modules/punycode": { @@ -2904,6 +5237,22 @@ "node": ">=6" } }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2924,20 +5273,11 @@ } ] }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true }, "node_modules/require-directory": { "version": "2.1.1", @@ -2956,6 +5296,23 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -2986,6 +5343,15 @@ "node": ">=4" } }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -2996,21 +5362,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -3032,28 +5383,7 @@ ], "dependencies": { "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + } }, "node_modules/semver": { "version": "7.6.3", @@ -3067,40 +5397,6 @@ "node": ">=10" } }, - "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", - "dev": true, - "dependencies": { - "type-fest": "^0.13.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, - "license": "ISC" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3128,21 +5424,38 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, - "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, "node_modules/sprintf-js": { @@ -3172,48 +5485,17 @@ "node": ">=8" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, - "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "node": ">=10" } }, "node_modules/strip-ansi": { @@ -3237,18 +5519,13 @@ "node": ">=8" } }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, - "optional": true, - "peer": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/strip-json-comments": { @@ -3263,95 +5540,106 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supertap": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/supertap/-/supertap-3.0.1.tgz", - "integrity": "sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==", + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "dependencies": { - "indent-string": "^5.0.0", - "js-yaml": "^3.14.1", - "serialize-error": "^7.0.1", - "strip-ansi": "^7.0.1" + "has-flag": "^4.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=8" } }, - "node_modules/supertap/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/supertap/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=8" } }, - "node_modules/supertap/node_modules/strip-ansi": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", - "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", "dev": true, "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "node_modules/through2/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, - "license": "ISC", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "node_modules/temp-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "engines": { - "node": ">=14.16" + "dependencies": { + "safe-buffer": "~5.1.0" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "dev": true }, - "node_modules/time-zone": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", - "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, "engines": { "node": ">=4" @@ -3370,13 +5658,6 @@ "node": ">=8.0" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -3389,6 +5670,54 @@ "typescript": ">=4.2.0" } }, + "node_modules/ts-jest": { + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "dev": true, + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -3444,6 +5773,27 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typescript": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", @@ -3463,17 +5813,43 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "license": "MIT" }, - "node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", "dev": true, - "license": "MIT", "engines": { - "node": ">=18" + "node": ">=8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, "node_modules/uri-js": { @@ -3498,31 +5874,27 @@ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/well-known-symbols": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", - "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, "engines": { - "node": ">=6" + "node": ">=10.12.0" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, - "license": "MIT", "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "makeerror": "1.0.12" } }, "node_modules/which": { @@ -3540,48 +5912,6 @@ "node": ">= 8" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wide-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/wide-align/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/wide-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -3649,29 +5979,13 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", "dev": true, "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.4" } }, "node_modules/y18n": { @@ -3683,12 +5997,6 @@ "node": ">=10" } }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index a603cb9..c654d7c 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,12 @@ "./src/*": "./src/*.ts" }, "scripts": { - "build": "tsc", + "build": "tsc && copyfiles -u 2 \"src/schemas/*\" lib/schemas", "lint": "tsc && eslint \"**/*.{js,ts}\"", "lint:fix": "tsc --noEmit && eslint \"**/*.{js,ts}\" --quiet --fix", "prettier": "prettier --check \"**/*.{js,ts}\"", "prettier:fix": "prettier --write \"**/*.{js,ts}\"", - "test": "ava", + "test": "jest --coverage", "prepublish": "tsc" }, "dependencies": { @@ -30,29 +30,23 @@ "@types/papaparse": "^5.3.14", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", + "lodash": "^4.17.21", "papaparse": "^5.4.1" }, "devDependencies": { + "@types/jest": "^29.5.13", + "@types/lodash": "^4.17.7", "@typescript-eslint/eslint-plugin": "^8.5.0", "@typescript-eslint/parser": "^8.5.0", - "ava": "^6.1.3", + "copyfiles": "^2.4.1", "eslint": "^9.10.0", "eslint-config-prettier": "^9.1.0", "esm": "^3.2.25", + "jest": "^29.7.0", + "jest-extended": "^4.0.2", "prettier": "^3.3.3", + "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "typescript": "^5.6.0" - }, - "ava": { - "files": [ - "test/**/*.spec.*" - ], - "extensions": { - "ts": "module" - }, - "nodeArguments": [ - "--loader=ts-node/esm", - "--experimental-specifier-resolution=node" - ] } } diff --git a/src/csv.ts b/src/csv.ts index f4c4b48..e726c0f 100644 --- a/src/csv.ts +++ b/src/csv.ts @@ -1,42 +1,9 @@ import { ValidationResult, - CsvValidationError, - CsvValidatorVersion, - CsvValidationOptions, SchemaVersion, -} from "./types.js" -import { - csvErrorToValidationError, - rowIsEmpty, - csvCellName, - objectFromKeysValues, - cleanColumnNames, -} from "./versions/common/csv.js" -import { CsvValidatorOneOne } from "./versions/1.1/csv.js" -import { CsvValidatorTwoZero } from "./versions/2.0/csv.js" -import { addErrorsToList, removeBOM } from "./utils.js" - -import Papa from "papaparse" - -const ERRORS = { - INVALID_VERSION: "Invalid version supplied", - HEADER_ERRORS: - "Errors were found in the headers or values in rows 1 through 3, so the remaining rows were not evaluated.", - MIN_ROWS: "At least one row must be present", - HEADER_BLANK: (row: number) => - `Required headers must be defined on rows 1 and 3. Row ${row} is blank`, -} - -export function getValidator( - version: SchemaVersion -): CsvValidatorVersion | null { - if (version === "v1.1") { - return CsvValidatorOneOne - } else if (version === "v2.0" || version === "v2.0.0") { - return CsvValidatorTwoZero - } - return null -} + CsvValidationOptions, +} from "./types.js"; +import { CsvValidator } from "./validators"; /** * @@ -49,153 +16,9 @@ export async function validateCsv( version: SchemaVersion, options: CsvValidationOptions = {} ): Promise { - let index = 0 - const errors: CsvValidationError[] = [] - const counts = { - errors: 0, - warnings: 0, - } - let headerColumns: string[] - let dataColumns: string[] - let tall = false - let validator: CsvValidatorVersion - - const requestedValidator = getValidator(version) - if (requestedValidator === null) { - return new Promise((resolve) => { - resolve({ - valid: false, - errors: [ - { - path: csvCellName(0, 0), - message: ERRORS.INVALID_VERSION, - }, - ], - }) - }) - } else { - validator = requestedValidator - } - - const handleParseStep = ( - step: Papa.ParseStepResult, - resolve: (result: ValidationResult | PromiseLike) => void, - parser: Papa.Parser - ) => { - const row: string[] = step.data.map((item) => item.toLowerCase()) - const isEmpty: boolean = rowIsEmpty(row) - - // Headers must be in the proper row, abort if not - if (isEmpty && (index === 0 || index === 2)) { - resolve({ - valid: false, - errors: [ - { - path: csvCellName(0, 0), - message: ERRORS.HEADER_BLANK(index + 1), - }, - ], - }) - parser.abort() - } else if (isEmpty && index !== 1) { - ++index - return - } - - if (index === 0) { - headerColumns = row - } else if (index === 1) { - addErrorsToList( - validator.validateHeader(headerColumns, row), - errors, - options.maxErrors, - counts - ) - } else if (index === 2) { - dataColumns = cleanColumnNames(row) - addErrorsToList( - validator.validateColumns(dataColumns), - errors, - options.maxErrors, - counts - ) - if (counts.errors > 0) { - resolve({ - valid: false, - errors: errors.map(csvErrorToValidationError).concat({ - path: csvCellName(0, 0), - message: ERRORS.HEADER_ERRORS, - }), - }) - parser.abort() - } else { - tall = validator.isTall(dataColumns) - } - } else { - const cleanRow = objectFromKeysValues(dataColumns, row) - addErrorsToList( - validator.validateRow(cleanRow, index, dataColumns, !tall), - errors, - options.maxErrors, - counts - ) - if (options.onValueCallback) { - options.onValueCallback(cleanRow) - } - } - - if ( - options.maxErrors && - options.maxErrors > 0 && - counts.errors >= options.maxErrors - ) { - resolve({ - valid: false, - errors: errors.map(csvErrorToValidationError), - }) - parser.abort() - } - - ++index - } - - const handleParseEnd = ( - resolve: (result: ValidationResult | PromiseLike) => void - ): void => { - if (index < 4) { - resolve({ - valid: false, - errors: [ - { - path: csvCellName(0, 0), - message: ERRORS.MIN_ROWS, - }, - ], - }) - } else { - resolve({ - valid: counts.errors === 0, - errors: errors.map(csvErrorToValidationError), - }) - } - } - - return new Promise((resolve, reject) => { - Papa.parse(input, { - header: false, - // chunkSize: 64 * 1024, - beforeFirstChunk: (chunk) => { - return removeBOM(chunk) - }, - step: (row: Papa.ParseStepResult, parser: Papa.Parser) => { - try { - handleParseStep(row, resolve, parser) - } catch (e) { - reject(e) - } - }, - complete: () => handleParseEnd(resolve), - error: (error: Error) => reject(error), - }) - }) + // currently, the CsvValidator takes options in the constructor, + // but the JsonValidator takes them when validating. + // we should pick one way, and stick with it. + const validator = new CsvValidator(version, options); + return validator.validate(input); } diff --git a/src/errors/ValidationError.ts b/src/errors/ValidationError.ts new file mode 100644 index 0000000..f8ef548 --- /dev/null +++ b/src/errors/ValidationError.ts @@ -0,0 +1,13 @@ +export class ValidationError { + public field: string | undefined + + constructor( + public path: string, + public message: string + ) {} + + withField(field: string): ValidationError { + this.field = field + return this + } +} diff --git a/src/errors/csv/AllowedValuesError.ts b/src/errors/csv/AllowedValuesError.ts new file mode 100644 index 0000000..ea244d5 --- /dev/null +++ b/src/errors/csv/AllowedValuesError.ts @@ -0,0 +1,19 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class AllowedValuesError extends CsvValidationError { + constructor( + row: number, + column: number, + public columnName: string, + public value: string, + public allowedValues: string[] + ) { + super( + row, + column, + `"${columnName}" value "${value}" is not one of the allowed valid values. You must encode one of these valid values: ${allowedValues.join( + ", " + )}` + ); + } +} diff --git a/src/errors/csv/AmbiguousFormatError.ts b/src/errors/csv/AmbiguousFormatError.ts new file mode 100644 index 0000000..a9fee57 --- /dev/null +++ b/src/errors/csv/AmbiguousFormatError.ts @@ -0,0 +1,11 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class AmbiguousFormatError extends CsvValidationError { + constructor() { + super( + 2, + -1, + 'Required payer-specific information data element headers are missing or miscoded from the MRF that does not follow the specifications for the CSV "Tall" or CSV "Wide" format.' + ); + } +} diff --git a/src/errors/csv/ColumnMissingError.ts b/src/errors/csv/ColumnMissingError.ts new file mode 100644 index 0000000..ef4504e --- /dev/null +++ b/src/errors/csv/ColumnMissingError.ts @@ -0,0 +1,11 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class ColumnMissingError extends CsvValidationError { + constructor(public columnName: string) { + super( + 2, + -1, + `Column ${columnName} is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary.` + ); + } +} diff --git a/src/errors/csv/CsvValidationError.ts b/src/errors/csv/CsvValidationError.ts new file mode 100644 index 0000000..3d50d1b --- /dev/null +++ b/src/errors/csv/CsvValidationError.ts @@ -0,0 +1,22 @@ +import { ValidationError } from "../ValidationError"; + +export class CsvValidationError extends ValidationError { + constructor(row: number, column: number, message: string) { + super(csvCellName(row, column), message); + } +} + +export const ASCII_UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +export function csvCellName(row: number, column: number): string { + return `${(column ?? -1) >= 0 ? csvColumnName(column) : "row "}${row + 1}`; +} + +export function csvColumnName(column: number): string { + let name = ""; + while (column >= 0) { + name = ASCII_UPPERCASE[column % ASCII_UPPERCASE.length] + name; + column = Math.floor(column / ASCII_UPPERCASE.length) - 1; + } + return name; +} diff --git a/src/errors/csv/DuplicateColumnError.ts b/src/errors/csv/DuplicateColumnError.ts new file mode 100644 index 0000000..814539c --- /dev/null +++ b/src/errors/csv/DuplicateColumnError.ts @@ -0,0 +1,14 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class DuplicateColumnError extends CsvValidationError { + constructor( + column: number, + public columnName: string + ) { + super( + 2, + column, + `Column ${columnName} duplicated in header. You must review and revise your column headers so that each header appears only once in the third row.` + ); + } +} diff --git a/src/errors/csv/DuplicateHeaderColumnError.ts b/src/errors/csv/DuplicateHeaderColumnError.ts new file mode 100644 index 0000000..9ee1441 --- /dev/null +++ b/src/errors/csv/DuplicateHeaderColumnError.ts @@ -0,0 +1,14 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class DuplicateHeaderColumnError extends CsvValidationError { + constructor( + column: number, + public columnName: string + ) { + super( + 0, + column, + `Column ${columnName} duplicated in header. You must review and revise your column headers so that each header appears only once in the first row.` + ); + } +} diff --git a/src/errors/csv/HeaderBlankError.ts b/src/errors/csv/HeaderBlankError.ts new file mode 100644 index 0000000..3406d81 --- /dev/null +++ b/src/errors/csv/HeaderBlankError.ts @@ -0,0 +1,11 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class HeaderBlankError extends CsvValidationError { + constructor(row: number) { + super( + row, + 0, + `Required headers must be defined on rows 1 and 3. Row ${row + 1} is blank` + ); + } +} diff --git a/src/errors/csv/HeaderColumnMissingError.ts b/src/errors/csv/HeaderColumnMissingError.ts new file mode 100644 index 0000000..4a3856f --- /dev/null +++ b/src/errors/csv/HeaderColumnMissingError.ts @@ -0,0 +1,11 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class HeaderColumnMissingError extends CsvValidationError { + constructor(public columnName: string) { + super( + 0, + -1, + `Header column "${columnName}" is miscoded or missing. You must include this header and confirm that it is encoded as specified in the data dictionary.` + ); + } +} diff --git a/src/errors/csv/InvalidDateError.ts b/src/errors/csv/InvalidDateError.ts new file mode 100644 index 0000000..f6d9eeb --- /dev/null +++ b/src/errors/csv/InvalidDateError.ts @@ -0,0 +1,16 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class InvalidDateError extends CsvValidationError { + constructor( + row: number, + column: number, + public columnName: string, + public value: string + ) { + super( + row, + column, + `"${columnName}" value "${value}" is not in a valid format. You must encode the date using the ISO 8601 format: YYYY-MM-DD or the month/day/year format: MM/DD/YYYY, M/D/YYYY` + ); + } +} diff --git a/src/errors/csv/InvalidStateCodeError.ts b/src/errors/csv/InvalidStateCodeError.ts new file mode 100644 index 0000000..d163fbb --- /dev/null +++ b/src/errors/csv/InvalidStateCodeError.ts @@ -0,0 +1,11 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class InvalidStateCodeError extends CsvValidationError { + constructor(column: number, stateCode: string) { + super( + 0, + column, + `${stateCode} is not an allowed value for state abbreviation. You must fill in the state or territory abbreviation even if there is no license number to encode. See the table found here for the list of valid values for state and territory abbreviations https://github.com/CMSgov/hospital-price-transparency/blob/master/documentation/CSV/state_codes.md` + ); + } +} diff --git a/src/errors/csv/InvalidVersionError.ts b/src/errors/csv/InvalidVersionError.ts new file mode 100644 index 0000000..6183aad --- /dev/null +++ b/src/errors/csv/InvalidVersionError.ts @@ -0,0 +1,7 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class InvalidVersionError extends CsvValidationError { + constructor() { + super(0, 0, "Invalid version supplied"); + } +} diff --git a/src/errors/csv/MinRowsError.ts b/src/errors/csv/MinRowsError.ts new file mode 100644 index 0000000..c7d522e --- /dev/null +++ b/src/errors/csv/MinRowsError.ts @@ -0,0 +1,7 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class MinRowsError extends CsvValidationError { + constructor() { + super(0, 0, "At least one row must be present"); + } +} diff --git a/src/errors/csv/ProblemsInHeaderError.ts b/src/errors/csv/ProblemsInHeaderError.ts new file mode 100644 index 0000000..04307c3 --- /dev/null +++ b/src/errors/csv/ProblemsInHeaderError.ts @@ -0,0 +1,11 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class ProblemsInHeaderError extends CsvValidationError { + constructor() { + super( + 0, + 0, + "Errors were found in the headers or values in rows 1 through 3, so the remaining rows were not evaluated." + ); + } +} diff --git a/src/errors/csv/RequiredValueError.ts b/src/errors/csv/RequiredValueError.ts new file mode 100644 index 0000000..838420b --- /dev/null +++ b/src/errors/csv/RequiredValueError.ts @@ -0,0 +1,16 @@ +import { CsvValidationError } from "./CsvValidationError"; + +export class RequiredValueError extends CsvValidationError { + constructor( + row: number, + column: number, + public columnName: string, + suffix: string = "" + ) { + super( + row, + column, + `A value is required for "${columnName}"${suffix}. You must encode the missing information.` + ); + } +} diff --git a/src/errors/csv/index.ts b/src/errors/csv/index.ts new file mode 100644 index 0000000..80cb0de --- /dev/null +++ b/src/errors/csv/index.ts @@ -0,0 +1,14 @@ +export * from "./CsvValidationError"; +export * from "./AllowedValuesError"; +export * from "./AmbiguousFormatError"; +export * from "./ColumnMissingError"; +export * from "./DuplicateColumnError"; +export * from "./DuplicateHeaderColumnError"; +export * from "./HeaderBlankError"; +export * from "./HeaderColumnMissingError"; +export * from "./InvalidDateError"; +export * from "./InvalidStateCodeError"; +export * from "./InvalidVersionError"; +export * from "./MinRowsError"; +export * from "./ProblemsInHeaderError"; +export * from "./RequiredValueError"; diff --git a/src/errors/json/InvalidJsonError.ts b/src/errors/json/InvalidJsonError.ts new file mode 100644 index 0000000..ce417bb --- /dev/null +++ b/src/errors/json/InvalidJsonError.ts @@ -0,0 +1,10 @@ +import { ValidationError } from "../ValidationError"; + +export class InvalidJsonError extends ValidationError { + constructor(public originalError: Error) { + super( + "", + `JSON parsing error: ${originalError.message}. The validator is unable to review a syntactically invalid JSON file. Please ensure that your file is well-formatted JSON.` + ); + } +} diff --git a/src/index.ts b/src/index.ts index 8381a01..e56f49f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -export * from "./types.js" -export * from "./csv.js" -export * from "./json.js" -export * from "./filename.js" +export * from "./types.js"; +export * from "./csv.js"; +export * from "./json.js"; +export * from "./filename.js"; diff --git a/src/json.ts b/src/json.ts index 5de27a5..c8cf256 100644 --- a/src/json.ts +++ b/src/json.ts @@ -2,9 +2,8 @@ import { ValidationResult, SchemaVersion, JsonValidatorOptions, -} from "./types.js" -import { JsonValidatorOneOne } from "./versions/1.1/json.js" -import { JsonValidatorTwoZero } from "./versions/2.0/json.js" +} from "./types.js"; +import { JsonValidator } from "./validators/JsonValidator.js"; /** * @@ -17,15 +16,6 @@ export async function validateJson( version: SchemaVersion, options: JsonValidatorOptions = {} ): Promise { - if (version === "v1.1") { - return JsonValidatorOneOne.validateJson(jsonInput, options) - } else if (version === "v2.0" || version === "v2.0.0") { - return JsonValidatorTwoZero.validateJson(jsonInput, options) - } - return new Promise((resolve) => { - resolve({ - valid: false, - errors: [{ path: "/", message: `Invalid version "${version}" supplied` }], - }) - }) + const validator = new JsonValidator(version); + return validator.validate(jsonInput, options); } diff --git a/src/schemas/v2.0.0.json b/src/schemas/v2.0.0.json new file mode 100644 index 0000000..e53f8ef --- /dev/null +++ b/src/schemas/v2.0.0.json @@ -0,0 +1,301 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "code_information": { + "type": "object", + "properties": { + "code": { + "type": "string", + "minLength": 1 + }, + "type": { + "enum": [ + "CPT", + "HCPCS", + "ICD", + "DRG", + "MS-DRG", + "R-DRG", + "S-DRG", + "APS-DRG", + "AP-DRG", + "APR-DRG", + "TRIS-DRG", + "APC", + "NDC", + "HIPPS", + "LOCAL", + "EAPG", + "CDT", + "RC", + "CDM" + ], + "type": "string" + } + }, + "required": [ + "code", + "type" + ] + }, + "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" + ] + }, + "license_information": { + "type": "object", + "properties": { + "license_number": { + "type": "string", + "minLength": 1 + }, + "state": { + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" + ], + "type": "string" + } + }, + "required": [ + "state" + ] + }, + "standard_charges": { + "type": "object", + "properties": { + "minimum": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number", + "exclusiveMinimum": 0 + }, + "gross_charge": { + "type": "number", + "exclusiveMinimum": 0 + }, + "discounted_cash": { + "type": "number", + "exclusiveMinimum": 0 + }, + "setting": { + "enum": [ + "inpatient", + "outpatient", + "both" + ], + "type": "string" + }, + "payers_information": { + "type": "array", + "items": { + "$ref": "#/definitions/payers_information" + }, + "minItems": 1 + }, + "additional_generic_notes": { + "type": "string" + } + }, + "required": [ + "setting" + ] + }, + "standard_charge_information": { + "type": "object", + "properties": { + "description": { + "type": "string", + "minLength": 1 + }, + "code_information": { + "type": "array", + "items": { + "$ref": "#/definitions/code_information" + }, + "minItems": 1 + }, + "standard_charges": { + "type": "array", + "items": { + "$ref": "#/definitions/standard_charges" + }, + "minItems": 1 + } + }, + "required": [ + "description", + "code_information", + "standard_charges" + ] + }, + "payers_information": { + "type": "object", + "properties": { + "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" + }, + "standard_charge_percentage": { + "type": "number", + "exclusiveMinimum": 0 + }, + "estimated_amount": { + "type": "number", + "exclusiveMinimum": 0 + }, + "methodology": { + "enum": [ + "case rate", + "fee schedule", + "percent of total billed charges", + "per diem", + "other" + ], + "type": "string" + } + }, + "required": [ + "payer_name", + "plan_name", + "methodology" + ] + } + }, + "type": "object", + "properties": { + "hospital_name": { + "type": "string", + "minLength": 1 + }, + "hospital_address": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "last_updated_on": { + "type": "string", + "format": "date" + }, + "affirmation": { + "$ref": "#/definitions/affirmation" + }, + "license_information": { + "$ref": "#/definitions/license_information" + }, + "version": { + "type": "string", + "minLength": 1 + }, + "hospital_location": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "standard_charge_information": { + "type": "array", + "items": { + "$ref": "#/definitions/standard_charge_information" + }, + "minItems": 1 + } + }, + "required": [ + "hospital_name", + "standard_charge_information", + "last_updated_on", + "hospital_location", + "hospital_address", + "license_information", + "affirmation", + "version" + ] +} \ No newline at end of file diff --git a/src/schemas/v2.1.0.json b/src/schemas/v2.1.0.json new file mode 100644 index 0000000..b5d0dd4 --- /dev/null +++ b/src/schemas/v2.1.0.json @@ -0,0 +1,388 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "code_information": { + "type": "object", + "properties": { + "code": { + "type": "string", + "minLength": 1 + }, + "type": { + "enum": [ + "CPT", + "HCPCS", + "ICD", + "DRG", + "MS-DRG", + "R-DRG", + "S-DRG", + "APS-DRG", + "AP-DRG", + "APR-DRG", + "TRIS-DRG", + "APC", + "NDC", + "HIPPS", + "LOCAL", + "EAPG", + "CDT", + "RC", + "CDM" + ], + "type": "string" + } + }, + "required": [ + "code", + "type" + ] + }, + "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" + ] + }, + "license_information": { + "type": "object", + "properties": { + "license_number": { + "type": "string", + "minLength": 1 + }, + "state": { + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" + ], + "type": "string" + } + }, + "required": [ + "state" + ] + }, + "standard_charges": { + "type": "object", + "properties": { + "minimum": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number", + "exclusiveMinimum": 0 + }, + "gross_charge": { + "type": "number", + "exclusiveMinimum": 0 + }, + "discounted_cash": { + "type": "number", + "exclusiveMinimum": 0 + }, + "setting": { + "enum": [ + "inpatient", + "outpatient", + "both" + ], + "type": "string" + }, + "payers_information": { + "type": "array", + "items": { + "$ref": "#/definitions/payers_information" + }, + "minItems": 1 + }, + "additional_generic_notes": { + "type": "string" + } + }, + "required": [ + "setting" + ], + "anyOf": [ + { + "type": "object", + "required": [ + "gross_charge" + ] + }, + { + "type": "object", + "required": [ + "discounted_cash" + ] + }, + { + "type": "object", + "properties": { + "payers_information": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "required": [ + "standard_charge_dollar" + ] + }, + { + "type": "object", + "required": [ + "standard_charge_algorithm" + ] + }, + { + "type": "object", + "required": [ + "standard_charge_percentage" + ] + } + ] + } + } + }, + "required": [ + "payers_information" + ] + } + ], + "if": { + "type": "object", + "properties": { + "payers_information": { + "type": "array", + "contains": { + "type": "object", + "required": [ + "standard_charge_dollar" + ] + } + } + }, + "required": [ + "payers_information" + ] + }, + "then": { + "required": [ + "minimum", + "maximum" + ] + } + }, + "standard_charge_information": { + "type": "object", + "properties": { + "description": { + "type": "string", + "minLength": 1 + }, + "code_information": { + "type": "array", + "items": { + "$ref": "#/definitions/code_information" + }, + "minItems": 1 + }, + "standard_charges": { + "type": "array", + "items": { + "$ref": "#/definitions/standard_charges" + }, + "minItems": 1 + } + }, + "required": [ + "description", + "code_information", + "standard_charges" + ] + }, + "payers_information": { + "type": "object", + "properties": { + "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" + }, + "standard_charge_percentage": { + "type": "number", + "exclusiveMinimum": 0 + }, + "methodology": { + "enum": [ + "case rate", + "fee schedule", + "percent of total billed charges", + "per diem", + "other" + ], + "type": "string" + } + }, + "required": [ + "payer_name", + "plan_name", + "methodology" + ], + "if": { + "properties": { + "methodology": { + "const": "other" + } + }, + "required": [ + "methodology" + ] + }, + "then": { + "properties": { + "additional_payer_notes": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "additional_payer_notes" + ] + } + } + }, + "type": "object", + "properties": { + "hospital_name": { + "type": "string", + "minLength": 1 + }, + "hospital_address": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "last_updated_on": { + "type": "string", + "format": "date" + }, + "affirmation": { + "$ref": "#/definitions/affirmation" + }, + "license_information": { + "$ref": "#/definitions/license_information" + }, + "version": { + "type": "string", + "minLength": 1 + }, + "hospital_location": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "standard_charge_information": { + "type": "array", + "items": { + "$ref": "#/definitions/standard_charge_information" + }, + "minItems": 1 + } + }, + "required": [ + "hospital_name", + "standard_charge_information", + "last_updated_on", + "hospital_location", + "hospital_address", + "license_information", + "affirmation", + "version" + ] +} \ No newline at end of file diff --git a/src/schemas/v2.2.0.json b/src/schemas/v2.2.0.json new file mode 100644 index 0000000..74fb9de --- /dev/null +++ b/src/schemas/v2.2.0.json @@ -0,0 +1,524 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "code_information": { + "type": "object", + "properties": { + "code": { + "type": "string", + "minLength": 1 + }, + "type": { + "enum": [ + "CPT", + "HCPCS", + "ICD", + "DRG", + "MS-DRG", + "R-DRG", + "S-DRG", + "APS-DRG", + "AP-DRG", + "APR-DRG", + "TRIS-DRG", + "APC", + "NDC", + "HIPPS", + "LOCAL", + "EAPG", + "CDT", + "RC", + "CDM" + ], + "type": "string" + } + }, + "required": [ + "code", + "type" + ] + }, + "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" + ] + }, + "drug_information": { + "type": "object", + "properties": { + "unit": { + "type": "string", + "minLength": 1 + }, + "type": { + "enum": [ + "GR", + "ML", + "ME", + "UN", + "F2", + "GM", + "EA" + ], + "type": "string" + } + }, + "required": [ + "unit", + "type" + ] + }, + "license_information": { + "type": "object", + "properties": { + "license_number": { + "type": "string", + "minLength": 1 + }, + "state": { + "enum": [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FM", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MH", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PW", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VI", + "VA", + "WA", + "WV", + "WI", + "WY" + ], + "type": "string" + } + }, + "required": [ + "state" + ] + }, + "standard_charges": { + "type": "object", + "properties": { + "minimum": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number", + "exclusiveMinimum": 0 + }, + "gross_charge": { + "type": "number", + "exclusiveMinimum": 0 + }, + "discounted_cash": { + "type": "number", + "exclusiveMinimum": 0 + }, + "setting": { + "enum": [ + "inpatient", + "outpatient", + "both" + ], + "type": "string" + }, + "payers_information": { + "type": "array", + "items": { + "$ref": "#/definitions/payers_information" + }, + "minItems": 1 + }, + "additional_generic_notes": { + "type": "string" + } + }, + "required": [ + "setting" + ], + "anyOf": [ + { + "type": "object", + "required": [ + "gross_charge" + ] + }, + { + "type": "object", + "required": [ + "discounted_cash" + ] + }, + { + "type": "object", + "properties": { + "payers_information": { + "type": "array", + "items": { + "anyOf": [ + { + "type": "object", + "required": [ + "standard_charge_dollar" + ] + }, + { + "type": "object", + "required": [ + "standard_charge_algorithm" + ] + }, + { + "type": "object", + "required": [ + "standard_charge_percentage" + ] + } + ] + } + } + }, + "required": [ + "payers_information" + ] + } + ], + "if": { + "type": "object", + "properties": { + "payers_information": { + "type": "array", + "contains": { + "type": "object", + "required": [ + "standard_charge_dollar" + ] + } + } + }, + "required": [ + "payers_information" + ] + }, + "then": { + "required": [ + "minimum", + "maximum" + ] + } + }, + "modifier_information": { + "type": "object", + "properties": { + "description": { + "type": "string", + "minLength": 1 + }, + "code": { + "type": "string", + "minLength": 1 + }, + "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", + "minLength": 1 + }, + "plan_name": { + "type": "string", + "minLength": 1 + }, + "description": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "payer_name", + "plan_name", + "description" + ] + }, + "standard_charge_information": { + "type": "object", + "properties": { + "description": { + "type": "string", + "minLength": 1 + }, + "drug_information": { + "$ref": "#/definitions/drug_information" + }, + "code_information": { + "type": "array", + "items": { + "$ref": "#/definitions/code_information" + }, + "minItems": 1 + }, + "standard_charges": { + "type": "array", + "items": { + "$ref": "#/definitions/standard_charges" + }, + "minItems": 1 + } + }, + "required": [ + "description", + "code_information", + "standard_charges" + ], + "if": { + "type": "object", + "properties": { + "code_information": { + "type": "array", + "contains": { + "type": "object", + "properties": { + "type": { + "const": "NDC" + } + } + } + } + } + }, + "then": { + "required": [ + "drug_information" + ] + } + }, + "payers_information": { + "type": "object", + "properties": { + "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" + }, + "standard_charge_percentage": { + "type": "number", + "exclusiveMinimum": 0 + }, + "estimated_amount": { + "type": "number", + "exclusiveMinimum": 0 + }, + "methodology": { + "enum": [ + "case rate", + "fee schedule", + "percent of total billed charges", + "per diem", + "other" + ], + "type": "string" + } + }, + "required": [ + "payer_name", + "plan_name", + "methodology" + ], + "allOf": [ + { + "if": { + "properties": { + "methodology": { + "const": "other" + } + }, + "required": [ + "methodology" + ] + }, + "then": { + "properties": { + "additional_payer_notes": { + "type": "string", + "minLength": 1 + } + }, + "required": [ + "additional_payer_notes" + ] + } + }, + { + "if": { + "anyOf": [ + { + "required": [ + "standard_charge_percentage" + ] + }, + { + "required": [ + "standard_charge_algorithm" + ] + } + ], + "not": { + "required": [ + "standard_charge_dollar" + ] + } + }, + "then": { + "required": [ + "estimated_amount" + ] + } + } + ] + } + }, + "type": "object", + "properties": { + "hospital_name": { + "type": "string", + "minLength": 1 + }, + "hospital_address": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "last_updated_on": { + "type": "string", + "format": "date" + }, + "affirmation": { + "$ref": "#/definitions/affirmation" + }, + "license_information": { + "$ref": "#/definitions/license_information" + }, + "version": { + "type": "string", + "minLength": 1 + }, + "hospital_location": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "standard_charge_information": { + "type": "array", + "items": { + "$ref": "#/definitions/standard_charge_information" + }, + "minItems": 1 + }, + "modifier_information": { + "type": "array", + "items": { + "$ref": "#/definitions/modifier_information" + } + } + }, + "required": [ + "hospital_name", + "standard_charge_information", + "last_updated_on", + "hospital_location", + "hospital_address", + "license_information", + "affirmation", + "version" + ] +} \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index 16a601e..4d38d34 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,27 +1,34 @@ -import { JsonTypes } from "@streamparser/json" +import { JsonTypes } from "@streamparser/json"; +import { ValidationError } from "./errors/ValidationError"; -export const SCHEMA_VERSIONS = ["v1.1", "v2.0"] as const -type SchemaVersionTuple = typeof SCHEMA_VERSIONS -export type SchemaVersion = SchemaVersionTuple[number] +export const SCHEMA_VERSIONS = [ + "v1.1", + "v2.0", + "v2.0.0", + "v2.1.0", + "v2.2.0", +] as const; +type SchemaVersionTuple = typeof SCHEMA_VERSIONS; +export type SchemaVersion = SchemaVersionTuple[number]; -export interface ValidationError { - path: string - field?: string - message: string - warning?: boolean -} +// export interface ValidationError { +// path: string; +// field?: string; +// message: string; +// warning?: boolean; +// } export interface ValidationResult { - valid: boolean - errors: ValidationError[] + valid: boolean; + errors: ValidationError[]; } export interface CsvValidationError { - row: number - column: number - field?: string - message: string - warning?: boolean + row: number; + column: number; + field?: string; + message: string; + warning?: boolean; } /** @@ -31,34 +38,34 @@ export interface CsvValidationError { * common module. */ export interface CsvValidatorVersion { - validateHeader: (columns: string[], row: string[]) => CsvValidationError[] - validateColumns: (columns: string[]) => CsvValidationError[] + validateHeader: (columns: string[], row: string[]) => CsvValidationError[]; + validateColumns: (columns: string[]) => CsvValidationError[]; validateRow: ( row: { [key: string]: string }, index: number, columns: string[], wide: boolean - ) => CsvValidationError[] - isTall: (columns: string[]) => boolean + ) => CsvValidationError[]; + isTall: (columns: string[]) => boolean; } export interface CsvValidationOptions { - maxErrors?: number - onValueCallback?: (value: { [key: string]: string }) => void + maxErrors?: number; + onValueCallback?: (value: { [key: string]: string }) => void; } export interface JsonValidatorOptions { - maxErrors?: number + maxErrors?: number; onValueCallback?: ( val: JsonTypes.JsonPrimitive | JsonTypes.JsonStruct - ) => void + ) => void; } export interface JsonValidatorVersion { validate: ( jsonInput: File | NodeJS.ReadableStream, options: JsonValidatorOptions - ) => Promise + ) => Promise; } export const STATE_CODES = [ @@ -118,6 +125,6 @@ export const STATE_CODES = [ "WV", "WI", "WY", -] as const -type StateCodeTuple = typeof STATE_CODES -export type StateCode = StateCodeTuple[number] +] as const; +type StateCodeTuple = typeof STATE_CODES; +export type StateCode = StateCodeTuple[number]; diff --git a/src/utils.ts b/src/utils.ts index 822269b..8326ce8 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,7 @@ -export function addErrorsToList( +import { ErrorObject } from "ajv"; +import { ValidationError } from "./errors/ValidationError"; + +export function oldAddErrorsToList( newErrors: T[], errorList: T[], maxErrors = 0, @@ -6,42 +9,62 @@ export function addErrorsToList( ) { // if warning list is already full, don't add the new warnings if (maxErrors > 0 && counts.warnings >= maxErrors) { - newErrors = newErrors.filter((error) => error.warning !== true) + newErrors = newErrors.filter((error) => error.warning !== true); // only add enough to reach the limit if (counts.errors + newErrors.length > maxErrors) { - newErrors = newErrors.slice(0, maxErrors - counts.errors) + newErrors = newErrors.slice(0, maxErrors - counts.errors); } - errorList.push(...newErrors) - counts.errors += newErrors.length + errorList.push(...newErrors); + counts.errors += newErrors.length; } else { newErrors.forEach((error) => { if (error.warning) { if (maxErrors <= 0 || counts.warnings < maxErrors) { - errorList.push(error) - counts.warnings++ + errorList.push(error); + counts.warnings++; } } else { if (maxErrors <= 0 || counts.errors < maxErrors) { - errorList.push(error) - counts.errors++ + errorList.push(error); + counts.errors++; } } - }) + }); } - return counts + return counts; +} + +export function addItemsWithLimit( + newItems: T[], + mainList: T[], + maxItems = 0 +) { + if (maxItems > 0 && mainList.length + newItems.length > maxItems) { + newItems = newItems.slice(0, maxItems - mainList.length); + } + mainList.push(...newItems); + return mainList.length; +} + +export function errorObjectToValidationError( + error: ErrorObject +): ValidationError { + return new ValidationError(error.instancePath, error.message ?? "").withField( + error.instancePath.split("/").pop() ?? "" + ); } export function removeBOM(chunk: string): string { // strip utf-8 BOM: see https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 - const dataBuffer = Buffer.from(chunk) + const dataBuffer = Buffer.from(chunk); if ( dataBuffer.length > 2 && dataBuffer[0] === 0xef && dataBuffer[1] === 0xbb && dataBuffer[2] === 0xbf ) { - chunk = chunk.trimStart() + chunk = chunk.trimStart(); } - return chunk + return chunk; } diff --git a/src/validators/BaseValidator.ts b/src/validators/BaseValidator.ts new file mode 100644 index 0000000..dce5f45 --- /dev/null +++ b/src/validators/BaseValidator.ts @@ -0,0 +1,9 @@ +import { ValidationResult } from "../types" + +export abstract class BaseValidator { + abstract validate( + input: File | NodeJS.ReadableStream + ): Promise + + constructor(public fileType: "csv" | "json") {} +} diff --git a/src/validators/CsvValidator.ts b/src/validators/CsvValidator.ts new file mode 100644 index 0000000..4c07d66 --- /dev/null +++ b/src/validators/CsvValidator.ts @@ -0,0 +1,538 @@ +import Papa from "papaparse"; +import { + CsvValidationOptions, + STATE_CODES, + StateCode, + ValidationResult, +} from "../types"; +import { BaseValidator } from "./BaseValidator"; +import { removeBOM } from "../utils"; +import { + AllowedValuesError, + AmbiguousFormatError, + ColumnMissingError, + CsvValidationError, + DuplicateColumnError, + DuplicateHeaderColumnError, + HeaderBlankError, + HeaderColumnMissingError, + InvalidDateError, + InvalidStateCodeError, + InvalidVersionError, + MinRowsError, + ProblemsInHeaderError, + RequiredValueError, +} from "../errors/csv"; +import { range } from "lodash"; + +export const 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."; + +export const HEADER_COLUMNS = [ + "hospital_name", // string + "last_updated_on", // date + "version", // string - maybe one of the known versions? + "hospital_location", // string + "hospital_address", // string + "license_number | [state]", // string, check for valid postal code in header + AFFIRMATION, // "true" or "false" +]; + +export type ColumnDefinition = { + label: string; + required: boolean; + dataRequired?: boolean; +}; + +export function objectFromKeysValues( + keys: string[], + values: string[] +): { [key: string]: string } { + return Object.fromEntries( + keys.map((key, index) => [key, values[index]]).filter((entry) => entry) + ); +} + +export function sepColumnsEqual(colA: string, colB: string) { + const cleanA = colA.split("|").map((v) => v.trim().toUpperCase()); + const cleanB = colB.split("|").map((v) => v.trim().toUpperCase()); + return ( + cleanA.length === cleanB.length && + cleanA.every((a, idx: number) => a === cleanB[idx]) + ); +} + +export function matchesString(a: string, b: string): boolean { + return a.toLocaleUpperCase() === b.toLocaleUpperCase(); +} + +export function isValidDate(value: string) { + // required format is YYYY-MM-DD or MM/DD/YYYY or M/D/YYYY or MM/D/YYYY or M/DD/YYYY + const dateMatch1 = value.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/); + const dateMatch2 = value.match(/^(\d{4})-(\d{2})-(\d{2})$/); + + if (dateMatch1 != null) { + // UTC methods are used because "date-only forms are interpreted as a UTC time", + // as per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format + // check that the parsed date matches the input, to guard against e.g. February 31 + const matchYear = dateMatch1[3]; + const matchMonth = dateMatch1[1]; + const matchDate = dateMatch1[2]; + const expectedYear = parseInt(matchYear); + const expectedMonth = parseInt(matchMonth) - 1; + const expectedDate = parseInt(matchDate); + const parsedDate = new Date(value); + return ( + expectedYear === parsedDate.getUTCFullYear() && + expectedMonth === parsedDate.getUTCMonth() && + expectedDate === parsedDate.getUTCDate() + ); + } else if (dateMatch2 != null) { + const matchYear = dateMatch2[1]; + const matchMonth = dateMatch2[2]; + const matchDate = dateMatch2[3]; + const expectedYear = parseInt(matchYear); + const expectedMonth = parseInt(matchMonth) - 1; + const expectedDate = parseInt(matchDate); + const parsedDate = new Date(value); + return ( + expectedYear === parsedDate.getUTCFullYear() && + expectedMonth === parsedDate.getUTCMonth() && + expectedDate === parsedDate.getUTCDate() + ); + } + return false; +} + +export function validateRequiredEnumField( + row: number, + column: number, + columnName: string, + value: string, + allowedValues: string[], + suffix: string = "" +) { + if (!value) { + return [new RequiredValueError(row, column, columnName, suffix)]; + } else if ( + !allowedValues.some((allowedValue) => matchesString(value, allowedValue)) + ) { + return [ + new AllowedValuesError(row, column, columnName, value, allowedValues), + ]; + } else { + return []; + } +} + +export class CsvValidator extends BaseValidator { + public index = 0; + public isTall: boolean = false; + public headerColumns: string[] = []; + public dataColumns: string[] = []; + public errors: CsvValidationError[] = []; + public maxErrors: number; + public dataCallback?: CsvValidationOptions["onValueCallback"]; + static allowedVersions = ["v2.0.0", "v2.1.0", "v2.2.0"]; + + constructor( + public version: string, + options: CsvValidationOptions = {} + ) { + super("csv"); + this.maxErrors = options.maxErrors ?? 0; + this.dataCallback = options.onValueCallback; + } + + static isAllowedVersion(version: string): boolean { + return CsvValidator.allowedVersions.includes(version); + } + + reset() { + this.index = 0; + this.headerColumns = []; + this.dataColumns = []; + this.errors = []; + } + + validate(input: File | NodeJS.ReadableStream): Promise { + if (!CsvValidator.isAllowedVersion(this.version)) { + return new Promise((resolve) => { + resolve({ + valid: false, + errors: [new InvalidVersionError()], + }); + }); + } + + return new Promise((resolve, reject) => { + Papa.parse(input, { + header: false, + beforeFirstChunk: (chunk) => { + return removeBOM(chunk); + }, + step: (row: Papa.ParseStepResult, parser: Papa.Parser) => { + try { + this.handleParseStep(row, resolve, parser); + } catch (e) { + reject(e); + } + }, + complete: () => this.handleParseEnd(resolve), + error: (error: Error) => reject(error), + }); + }); + } + + validateHeaderColumns(columns: string[]): CsvValidationError[] { + const remainingColumns = [...HEADER_COLUMNS]; + const discoveredColumns: string[] = []; + const errors: CsvValidationError[] = []; + columns.forEach((column, index) => { + const matchingColumnIndex = remainingColumns.findIndex( + (requiredColumn) => { + if (requiredColumn === "license_number | [state]") { + // make a best guess as to when a header is meant to be the license_number header + // if it has two parts, and the first part matches, then the second part ought to be valid + const splitColumn = column.split("|").map((v) => v.trim()); + if (splitColumn.length !== 2) { + return false; + } + if (sepColumnsEqual(splitColumn[0], "license_number")) { + if ( + STATE_CODES.includes(splitColumn[1].toUpperCase() as StateCode) + ) { + return true; + } else { + errors.push(new InvalidStateCodeError(index, splitColumn[1])); + return false; + } + } else { + return false; + } + } else { + return sepColumnsEqual(column, requiredColumn); + } + } + ); + if (matchingColumnIndex > -1) { + discoveredColumns[index] = column; + remainingColumns.splice(matchingColumnIndex, 1); + } else { + // if we already found this column, it's a duplicate + const existingColumn = discoveredColumns.find((discovered) => { + return discovered != null && sepColumnsEqual(discovered, column); + }); + if (existingColumn) { + errors.push(new DuplicateHeaderColumnError(index, column)); + } + } + }); + + errors.push( + ...remainingColumns.map( + (requiredColumn) => new HeaderColumnMissingError(requiredColumn) + ) + ); + this.headerColumns = discoveredColumns; + return errors; + } + + validateHeaderRow(row: string[]): CsvValidationError[] { + const errors: CsvValidationError[] = []; + this.headerColumns.forEach((header, index) => { + if (/^license_number\s*\|\s*.{2}$/.test(header)) { + return; + } + if (header != null) { + const value = row[index] ?? ""; + if (!value) { + errors.push(new RequiredValueError(1, index, header)); + } else if ( + sepColumnsEqual(header, "last_updated_on") && + !isValidDate(value) + ) { + errors.push(new InvalidDateError(1, index, header, value)); + } else if (sepColumnsEqual(header, AFFIRMATION)) { + errors.push( + ...validateRequiredEnumField(1, index, header, value, [ + "true", + "false", + ]) + ); + } + } + }); + return errors; + } + + validateHeader(row: string[]): CsvValidationError[] { + return [ + ...this.validateHeaderColumns(this.headerColumns), + ...this.validateHeaderRow(row), + ]; + } + + validateColumns(columns: string[]): CsvValidationError[] { + this.isTall = this.areMyColumnsTall(columns); + const payersPlans = this.getPayersPlans(columns); + if (this.isTall === payersPlans.length > 0) { + return [new AmbiguousFormatError()]; + } + this.dataColumns = []; + const errors: CsvValidationError[] = []; + const codeCount = this.getCodeCount(columns); + const expectedDataColumns = CsvValidator.getExpectedDataColumns( + this.version, + codeCount, + payersPlans + ); + columns.forEach((column, index) => { + const matchingColumnIndex = expectedDataColumns.findIndex((expected) => { + return sepColumnsEqual(column, expected.label); + }); + if (matchingColumnIndex > -1) { + this.dataColumns[index] = column; + expectedDataColumns.splice(matchingColumnIndex, 1); + } else { + const isDuplicate = this.dataColumns.some((dataColumn) => { + return dataColumn != null && sepColumnsEqual(dataColumn, column); + }); + if (isDuplicate) { + errors.push(new DuplicateColumnError(index, column)); + } else { + this.dataColumns[index] = column; + } + } + }); + expectedDataColumns + .filter((expectedColumn) => expectedColumn.required) + .forEach((expectedColumn) => { + errors.push(new ColumnMissingError(expectedColumn.label)); + }); + return errors; + } + + getCodeCount(columns: string[]): number { + return Math.max( + 0, + ...columns + .map((c) => + c + .split("|") + .map((v) => v.trim()) + .filter((v) => !!v) + ) + .filter( + (c) => + c[0] === "code" && + (c.length === 2 || (c.length === 3 && c[2] === "type")) + ) + .map((c) => +c[1].replace(/\D/g, "")) + .filter((v) => !isNaN(v)) + ); + } + + static getExpectedDataColumns( + version: string, + codeCount: number, + payersPlans: string[] + ): ColumnDefinition[] { + const columns: ColumnDefinition[] = [ + { label: "description", required: true }, + { label: "setting", required: true }, + { label: "standard_charge | gross", required: true }, + { label: "standard_charge | discounted_cash", required: true }, + { label: "standard_charge | min", required: true }, + { label: "standard_charge | max", required: true }, + { label: "additional_generic_notes", required: true }, + ]; + range(1, Math.max(1, codeCount)).forEach((i) => { + columns.push( + { label: `code | ${i}`, required: true }, + { label: `code | ${i} | type`, required: true } + ); + }); + + if (payersPlans.length > 0) { + payersPlans.forEach((payerPlan) => { + columns.push( + { + label: `standard_charge | ${payerPlan} | negotiated_dollar`, + required: true, + }, + { + label: `standard_charge | ${payerPlan} | negotiated_percentage`, + required: true, + }, + { + label: `standard_charge | ${payerPlan} | negotiated_algorithm`, + required: true, + }, + { + label: `standard_charge | ${payerPlan} | methodology`, + required: true, + }, + { + label: `additional_payer_notes | ${payerPlan}`, + required: true, + } + ); + }); + } else { + columns.push( + { label: "payer_name", required: true }, + { label: "plan_name", required: true }, + { label: "standard_charge | negotiated_dollar", required: true }, + { label: "standard_charge | negotiated_percentage", required: true }, + { label: "standard_charge | negotiated_algorithm", required: true }, + { label: "standard_charge | methodology", required: true } + ); + } + + switch (version) { + case "v2.0.0": + // do we want to add these? + // it sort of depends on how other parts of the implement shake out. + // columns.push( + // { label: "drug_unit_of_measurement", required: false }, + // { label: "drug_type_of_measurement", required: false } + // ); + break; + case "v2.2.0": + columns.push( + { label: "drug_unit_of_measurement", required: true }, + { label: "drug_type_of_measurement", required: true }, + { label: "modifiers", required: true } + ); + if (payersPlans.length > 0) { + columns.push( + ...payersPlans.map((payerPlan) => { + return { + label: `estimated_amount | ${payerPlan}`, + required: true, + }; + }) + ); + } else { + columns.push({ label: "estimated_amount", required: true }); + } + break; + } + return columns; + } + + areMyColumnsTall(columns: string[]): boolean { + // "payer_name" and "plan_name" are required for the tall format, + // so if they are present, the columns are probably tall + return ["payer_name", "plan_name"].every((tallColumn) => { + return columns.some((column) => matchesString(column, tallColumn)); + }); + } + + getPayersPlans(columns: string[]): string[] { + // standard_charge | Payer ABC | Plan 1 | negotiated_dollar + // standard_charge | Payer ABC | Plan 1 | negotiated_percentage + // standard_charge | Payer ABC | Plan 1 | negotiated_algorithm + // standard_charge | Payer ABC | Plan 1 | methodology + // estimated_amount | Payer ABC | Plan 1 + // additional_payer_notes | Payer ABC | Plan 1 + const payersPlans = new Set(); + columns.forEach((column) => { + const splitColumn = column.split("|").map((p) => p.trim()); + if (splitColumn.length === 4) { + if ( + matchesString(splitColumn[0], "standard_charge") && + [ + "negotiated_dollar", + "negotiated_percentage", + "negotiated_algorithm", + "methodology", + ].some((p) => matchesString(p, splitColumn[3])) + ) { + payersPlans.add(splitColumn.slice(1, 3).join(" | ")); + } + } else if ( + splitColumn.length === 3 && + ["estimated_amount", "additional_payer_notes"].some((p) => + matchesString(p, splitColumn[0]) + ) + ) { + payersPlans.add(splitColumn.slice(1, 3).join(" | ")); + } + }); + return Array.from(payersPlans); + } + + validateDataRow(row: string[]): CsvValidationError[] { + // how do we want to manage this, because the requirements are going to change over time + // there may be newly required things, or un-required things + throw new Error("not implemented yet"); + } + + handleParseStep( + step: Papa.ParseStepResult, + resolve: (value: ValidationResult | PromiseLike) => void, + parser: Papa.Parser + ) { + const row: string[] = step.data.map((value) => value.trim()); + const isEmpty = row.every((value) => value === ""); + if (isEmpty && (this.index === 0 || this.index === 2)) { + resolve({ + valid: false, + errors: [new HeaderBlankError(this.index)], + }); + parser.abort(); + } else if (isEmpty && this.index !== 1) { + this.index++; + return; + } + + if (this.index === 0) { + this.headerColumns = row; + } else if (this.index === 1) { + this.errors.push(...this.validateHeader(row)); + } else if (this.index === 2) { + this.errors.push(...this.validateColumns(row)); + if (this.errors.length > 0) { + resolve({ + valid: false, + errors: [...this.errors, new ProblemsInHeaderError()], + }); + parser.abort(); + } + } else { + // regular data row + this.errors.push(...this.validateDataRow(row)); + if (this.dataCallback) { + const cleanRow = objectFromKeysValues(this.dataColumns, row); + this.dataCallback(cleanRow); + } + } + + if (this.maxErrors > 0 && this.errors.length >= this.maxErrors) { + resolve({ + valid: false, + errors: this.errors, + }); + parser.abort(); + } + this.index++; + } + + handleParseEnd( + resolve: (value: ValidationResult | PromiseLike) => void + ): void { + if (this.index < 4) { + resolve({ + valid: false, + errors: [new MinRowsError()], + }); + } else { + resolve({ + valid: this.errors.length === 0, + errors: this.errors, + }); + } + } +} diff --git a/src/validators/JsonValidator.ts b/src/validators/JsonValidator.ts new file mode 100644 index 0000000..cc7d115 --- /dev/null +++ b/src/validators/JsonValidator.ts @@ -0,0 +1,188 @@ +import fs from "fs"; +import path from "path"; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; +import { JSONParser } from "@streamparser/json"; +import { JsonValidatorOptions, ValidationResult } from "../types"; +import { BaseValidator } from "./BaseValidator"; +import { + addItemsWithLimit, + errorObjectToValidationError, + removeBOM, +} from "../utils"; +import { ValidationError } from "../errors/ValidationError"; +import { InvalidJsonError } from "../errors/json/InvalidJsonError"; + +export class JsonValidator extends BaseValidator { + private fullSchema: any; + private standardChargeSchema: any; + private metadataSchema: any; + + constructor(public version: string) { + super("json"); + try { + this.fullSchema = JSON.parse( + fs.readFileSync( + path.join(__dirname, "..", "schemas", `${version}.json`), + "utf-8" + ) + ); + this.buildStandardChargeSchema(); + this.buildMetadataSchema(); + } catch (err) { + console.log(err); + throw Error(`Could not load JSON schema with version: ${version}`); + } + } + + buildStandardChargeSchema() { + this.standardChargeSchema = { + $schema: this.fullSchema["$schema"], + definitions: this.fullSchema.definitions, + ...this.fullSchema.definitions.standard_charge_information, + }; + } + + buildMetadataSchema() { + this.metadataSchema = { ...this.fullSchema }; + delete this.metadataSchema.properties.standard_charge_information; + this.metadataSchema.required = this.metadataSchema.required.filter( + (propertyName: string) => propertyName !== "standard_charge_information" + ); + } + + async validate( + input: File | NodeJS.ReadableStream, + options: JsonValidatorOptions = {} + ): Promise { + const validator = new Ajv({ allErrors: true }); + addFormats(validator); + const parser = new JSONParser({ + paths: [ + "$.hospital_name", + "$.last_updated_on", + "$.license_information", + "$.version", + "$.hospital_address", + "$.hospital_location", + "$.affirmation", + "$.modifier_information", + "$.standard_charge_information.*", + ], + keepStack: false, + }); + const metadata: { [key: string]: any } = {}; + let valid = true; + let hasCharges = false; + + return new Promise(async (resolve) => { + const errors: ValidationError[] = []; + parser.onValue = ({ value, key, stack }) => { + if (stack.length > 2 || key === "standard_charge_information") return; + if (typeof key === "string") { + metadata[key] = value; + } else { + hasCharges = true; + if (!validator.validate(this.standardChargeSchema, value)) { + const pathPrefix = stack + .filter((se) => se.key) + .map((se) => se.key) + .join("/"); + const newErrors = + validator.errors?.map(errorObjectToValidationError) ?? []; + newErrors.forEach((error) => { + error.path = `/${pathPrefix}/${key}${error.path}`; + }); + addItemsWithLimit(newErrors, errors, options.maxErrors); + valid = errors.length === 0; + } + if (options.onValueCallback && value != null) { + options.onValueCallback(value); + } + if ( + options.maxErrors && + options.maxErrors > 0 && + errors.length >= options.maxErrors + ) { + resolve({ + valid: false, + errors: errors, + }); + parser.end(); + } + } + }; + + parser.onEnd = () => { + // If no charges present, use the full schema to throw error for missing + if ( + !validator.validate( + hasCharges ? this.metadataSchema : this.fullSchema, + metadata + ) + ) { + const newErrors = + validator.errors?.map(errorObjectToValidationError) ?? []; + addItemsWithLimit(newErrors, errors, options.maxErrors); + valid = errors.length === 0; + } + resolve({ + valid, + errors, + }); + }; + + parser.onError = (e) => { + parser.onEnd = () => null; + parser.onError = () => null; + parser.end(); + resolve({ + valid: false, + errors: [new InvalidJsonError(e)], + }); + }; + + parseJson(input, parser); + }); + } +} + +export async function parseJson( + jsonInput: File | NodeJS.ReadableStream, + parser: JSONParser +): Promise { + if (typeof window !== "undefined" && jsonInput instanceof File) { + const stream = jsonInput.stream(); + const reader = stream.getReader(); + const textDecoder = new TextDecoder("utf-8"); + + function readChunk() { + return reader.read().then(({ done, value }) => { + if (done) { + parser.end(); + return; + } + + parser.write(textDecoder.decode(value)); + readChunk(); + }); + } + + readChunk(); + } else { + let firstChunk = true; + const jsonStream = jsonInput as NodeJS.ReadableStream; + jsonStream.on("end", () => parser.end()); + jsonStream.on("error", (e) => { + throw e; + }); + jsonStream.on("data", (data: string) => { + // strip utf-8 BOM: see https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 + if (firstChunk) { + data = removeBOM(data); + firstChunk = false; + } + parser.write(data); + }); + } +} diff --git a/src/validators/index.ts b/src/validators/index.ts new file mode 100644 index 0000000..aa92312 --- /dev/null +++ b/src/validators/index.ts @@ -0,0 +1,3 @@ +export * from "./BaseValidator"; +export * from "./CsvValidator"; +export * from "./JsonValidator"; diff --git a/src/versions/1.1/csv.ts b/src/versions/1.1/csv.ts deleted file mode 100644 index 478838c..0000000 --- a/src/versions/1.1/csv.ts +++ /dev/null @@ -1,572 +0,0 @@ -import { CsvValidationError, StateCode, STATE_CODES } from "../../types.js" - -import { - csvErr, - sepColumnsEqual, - parseSepField, - getCodeCount, -} from "../common/csv.js" -import { - BILLING_CODE_TYPES, - BillingCodeType, - CHARGE_SETTINGS, - CONTRACTING_METHODS, - ChargeSetting, - ContractingMethod, - DRUG_UNITS, - DrugUnit, -} from "./types.js" - -export const HEADER_COLUMNS = [ - "hospital_name", - "last_updated_on", - "version", - "hospital_location", - "license_number | state", -] - -export const BASE_COLUMNS = [ - "description", - "setting", - "drug_unit_of_measurement", - "drug_type_of_measurement", - "modifiers", - "standard_charge | gross", - "standard_charge | discounted_cash", -] - -export const MIN_MAX_COLUMNS = [ - "standard_charge | min", - "standard_charge | max", -] - -export const TALL_COLUMNS = [ - "payer_name", - "plan_name", - "standard_charge | negotiated_dollar", - "standard_charge | negotiated_percent", - "standard_charge | contracting_method", - "additional_generic_notes", -] - -const ERRORS = { - HEADER_COLUMN_NAME: (actual: string, expected: string) => - `Header column is "${actual}", it should be "${expected}"`, - HEADER_COLUMN_MISSING: (column: string) => - `Header column should be "${column}", but it is not present`, - HEADER_COLUMN_BLANK: (column: string) => `"${column}" is blank`, - HEADER_STATE_CODE: (column: string, stateCode: string) => - `Header column "${column}" includes an invalid state code "${stateCode}"`, - COLUMN_NAME: (actual: string, expected: string, format: string) => - `Column is "${actual}" and should be "${expected}" for ${format} format`, - COLUMN_MISSING: (column: string, format: string) => - `Column ${column} is missing, but it is required for ${format} format`, - NOTES_COLUMN: (column: string) => - `The last column should be "additional_generic_notes", is "${column}"`, - ALLOWED_VALUES: (column: string, value: string, allowedValues: string[]) => - `"${column}" value "${value}" is not one of the allowed values: ${allowedValues - .map((t) => `"${t}"`) - .join(", ")}`, - INVALID_NUMBER: (column: string, value: string) => - `"${column}" value "${value}" is not a valid number`, - POSITIVE_NUMBER: (column: string, suffix = ``) => - `"${column}" is required to be a positive number${suffix}`, - CHARGE_ONE_REQUIRED: (column: string) => { - const fieldName = column.replace(" | percent", "") - return `One of "${fieldName}" or "${fieldName} | percent" is required` - }, - REQUIRED: (column: string, suffix = ``) => `"${column}" is required${suffix}`, - CHARGE_PERCENT_REQUIRED_SUFFIX: " (one of charge or percent is required)", -} - -/** @private */ -export function validateHeader( - columns: string[], - row: string[] -): CsvValidationError[] { - const { errors: headerErrors, columns: headerColumns } = - validateHeaderColumns(columns) - const rowErrors = validateHeaderRow(headerColumns, row) - return [...headerErrors, ...rowErrors] -} - -/** @private */ -export function validateHeaderColumns(columns: string[]): { - errors: CsvValidationError[] - columns: (string | undefined)[] -} { - const rowIndex = 0 - const remainingColumns = [...HEADER_COLUMNS] - const discoveredColumns: string[] = [] - columns.forEach((column, index) => { - const matchingColumnIndex = remainingColumns.findIndex((requiredColumn) => { - if (requiredColumn === "license_number | state") { - // see if it works - const licenseStateErrors = validateLicenseStateColumn( - column, - rowIndex, - index - ) - return licenseStateErrors.length === 0 - } else { - return sepColumnsEqual(column, requiredColumn) - } - }) - if (matchingColumnIndex > -1) { - discoveredColumns[index] = column - remainingColumns.splice(matchingColumnIndex, 1) - } - }) - return { - errors: remainingColumns.map((requiredColumn) => { - return csvErr( - rowIndex, - columns.length, - requiredColumn, - ERRORS.HEADER_COLUMN_MISSING(requiredColumn) - ) - }), - columns: discoveredColumns, - } -} - -/** @private */ -export function validateHeaderRow( - headers: (string | undefined)[], - row: string[] -): CsvValidationError[] { - const errors: CsvValidationError[] = [] - const rowIndex = 1 - - headers.forEach((header, index) => { - if (header != null) { - if (!row[index]?.trim()) { - errors.push( - csvErr(rowIndex, index, header, ERRORS.HEADER_COLUMN_BLANK(header)) - ) - } - } - }) - - return errors -} - -/** @private */ -export function validateColumns(columns: string[]): CsvValidationError[] { - const rowIndex = 2 - - const tall = isTall(columns) - - const baseColumns = getBaseColumns(columns) - const wideColumns = getWideColumns(columns) - const tallColumns = getTallColumns(columns) - const schemaFormat = tall ? "tall" : "wide" - const remainingColumns = baseColumns.concat(tall ? tallColumns : wideColumns) - - columns.forEach((column) => { - const matchingColumnIndex = remainingColumns.findIndex((requiredColumn) => - sepColumnsEqual(column, requiredColumn) - ) - if (matchingColumnIndex > -1) { - remainingColumns.splice(matchingColumnIndex, 1) - } - }) - - return remainingColumns.map((requiredColumn) => { - return csvErr( - rowIndex, - columns.length, - requiredColumn, - ERRORS.COLUMN_MISSING(requiredColumn, schemaFormat) - ) - }) -} - -/** @private */ -export function validateRow( - row: { [key: string]: string }, - index: number, - columns: string[], - wide = false -): CsvValidationError[] { - const errors: CsvValidationError[] = [] - - const requiredFields = ["description", "code | 1"] - requiredFields.forEach((field) => - errors.push( - ...validateRequiredField(row, field, index, columns.indexOf(field)) - ) - ) - - if ( - !BILLING_CODE_TYPES.includes( - row["code | 1 | type"].toUpperCase() as BillingCodeType - ) - ) { - errors.push( - csvErr( - index, - columns.indexOf("code | 1 | type"), - "code | 1 | type", - ERRORS.ALLOWED_VALUES( - "code | 1 | type", - row["code | 1 | type"], - BILLING_CODE_TYPES as unknown as string[] - ), - true - ) - ) - } - - // TODO: Code itself is required, need to check all of those, not all checked here - if ( - row["code | 2"] && - !BILLING_CODE_TYPES.includes( - row["code | 2 | type"].toUpperCase() as BillingCodeType - ) - ) { - errors.push( - csvErr( - index, - columns.indexOf("code | 2 | type"), - "code | 2 | type", - ERRORS.ALLOWED_VALUES( - "code | 2 | type", - row["code | 2 | type"], - BILLING_CODE_TYPES as unknown as string[] - ) - ) - ) - } - - if (!CHARGE_SETTINGS.includes(row["setting"] as ChargeSetting)) { - errors.push( - csvErr( - index, - columns.indexOf("setting"), - "setting", - ERRORS.ALLOWED_VALUES( - "setting", - row["setting"], - CHARGE_SETTINGS as unknown as string[] - ) - ) - ) - } - - if (row["drug_unit_of_measurement"]) { - if (!/\d+(\.\d+)?/g.test(row["drug_unit_of_measurement"])) { - errors.push( - csvErr( - index, - columns.indexOf("drug_unit_of_measurement"), - "drug_unit_of_measurement", - ERRORS.INVALID_NUMBER( - "drug_unit_of_measurement", - row["drug_unit_of_measurement"] - ) - ) - ) - } - if ( - !DRUG_UNITS.includes( - row["drug_type_of_measurement"].toUpperCase() as DrugUnit - ) - ) { - errors.push( - csvErr( - index, - columns.indexOf("drug_type_of_measurement"), - "drug_type_of_measurement", - ERRORS.ALLOWED_VALUES( - "drug_type_of_measurement", - row["drug_type_of_measurement"], - DRUG_UNITS as unknown as string[] - ) - ) - ) - } - } - - const chargeFields = [ - "standard_charge | gross", - "standard_charge | discounted_cash", - "standard_charge | min", - "standard_charge | max", - ] - chargeFields.forEach((field) => - errors.push( - ...validateRequiredFloatField(row, field, index, columns.indexOf(field)) - ) - ) - - if (wide) { - errors.push(...validateWideFields(row, index)) - } else { - errors.push(...validateTallFields(row, index)) - } - - return errors -} - -/** @private */ -export function validateWideFields( - row: { [key: string]: string }, - index: number -): CsvValidationError[] { - const errors: CsvValidationError[] = [] - // TODO: Is checking that all are present covered in checking columns? - // TODO: Is order maintained on entries? likely not - Object.entries(row).forEach(([field, value], columnIndex) => { - if ( - field.includes("contracting_method") && - !CONTRACTING_METHODS.includes(value as ContractingMethod) - ) { - errors.push( - csvErr( - index, - BASE_COLUMNS.length + columnIndex, - field, - ERRORS.ALLOWED_VALUES( - field, - value, - CONTRACTING_METHODS as unknown as string[] - ) - ) - ) - } else if (field.includes("standard_charge")) { - if ( - field.includes(" | percent") && - !value.trim() && - !row[field.replace(" | percent", "")].trim() - ) { - errors.push( - csvErr( - index, - BASE_COLUMNS.length + columnIndex, - field, // TODO: Might be different? - ERRORS.CHARGE_ONE_REQUIRED(field) - ) - ) - } - } - }) - return errors -} - -function validateLicenseStateColumn( - column: string, - rowIndex: number, - columnIndex: number -): CsvValidationError[] { - const LICENSE_STATE = "license_number | state" - const invalidMessage = ERRORS.HEADER_COLUMN_NAME( - column, - "license_number | " - ) - const splitColumn = column.split("|").map((v) => v.trim()) - if (splitColumn.length !== 2) { - return [csvErr(rowIndex, columnIndex, LICENSE_STATE, invalidMessage)] - } - const stateCode = column.split("|").slice(-1)[0].trim() - if (!STATE_CODES.includes(stateCode.toUpperCase() as StateCode)) { - return [ - csvErr( - rowIndex, - columnIndex, - LICENSE_STATE, - ERRORS.HEADER_STATE_CODE(column, stateCode) - ), - ] - } else if (!sepColumnsEqual(column, `license_number | ${stateCode}`)) { - return [csvErr(rowIndex, columnIndex, LICENSE_STATE, invalidMessage)] - } - return [] -} - -/** @private */ -export function validateTallFields( - row: { [key: string]: string }, - index: number -): CsvValidationError[] { - const errors: CsvValidationError[] = [] - - const requiredFields = ["payer_name", "plan_name"] - requiredFields.forEach((field) => - errors.push( - ...validateRequiredField( - row, - field, - index, - BASE_COLUMNS.length + TALL_COLUMNS.indexOf(field) - ) - ) - ) - - // TODO: Only one of these has to be filled, clarify error - const floatFields = [ - "standard_charge | negotiated_dollar", - "standard_charge | negotiated_percent", - ] - const floatErrors = floatFields.flatMap((field) => - validateRequiredFloatField( - row, - field, - index, - BASE_COLUMNS.length + TALL_COLUMNS.indexOf(field), - ERRORS.CHARGE_PERCENT_REQUIRED_SUFFIX - ) - ) - // TODO: Is it an error if both fields are present? - // Only one of these has to be filled, so if only one errors out ignore it - if (floatErrors.length > 1) { - errors.push(...floatErrors) - } - - if ( - !CONTRACTING_METHODS.includes( - row["standard_charge | contracting_method"] as ContractingMethod - ) - ) { - errors.push( - csvErr( - index, - BASE_COLUMNS.indexOf("standard_charge | contracting_method"), - // TODO: Change to constants - "standard_charge | contracting_method", - ERRORS.ALLOWED_VALUES( - "standard_charge | contracting_method", - row["standard_charge | contracting_method"], - CONTRACTING_METHODS as unknown as string[] - ) - ) - ) - } - - return errors -} - -/** @private */ -export function getBaseColumns(columns: string[]): string[] { - const codeCount = Math.max(1, getCodeCount(columns)) - const codeColumns = Array(codeCount) - .fill(0) - .flatMap((_, i) => [`code | ${i + 1}`, `code | ${i + 1} | type`]) - - return [ - "description", - ...codeColumns, - "setting", - "drug_unit_of_measurement", - "drug_type_of_measurement", - "modifiers", - "standard_charge | gross", - "standard_charge | discounted_cash", - ] -} - -/** @private */ -export function getWideColumns(columns: string[]): string[] { - const payersPlans = getPayersPlans(columns) - const payersPlansColumns: string[] = payersPlans - .flatMap((payerPlan) => [ - ["standard_charge", ...payerPlan], - ["standard_charge", ...payerPlan, "percent"], - ["standard_charge", ...payerPlan, "contracting_method"], - ["additional_payer_notes", ...payerPlan], - ]) - .map((c) => c.join(" | ")) - - return [ - ...payersPlansColumns.slice(0, 2), - ...MIN_MAX_COLUMNS, - ...payersPlansColumns.slice(2), - "additional_generic_notes", - ] -} - -/** @private */ -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export function getTallColumns(columns: string[]): string[] { - return [ - "payer_name", - "plan_name", - "standard_charge | negotiated_dollar", - "standard_charge | negotiated_percent", - "standard_charge | min", - "standard_charge | max", - "standard_charge | contracting_method", - "additional_generic_notes", - ] -} - -function getPayersPlans(columns: string[]): string[][] { - const excludeSegments = [ - "standard_charge", - "min", - "max", - "gross", - "discounted_cash", - "contracting_method", - ] - return Array.from( - new Set( - columns - .filter((c) => c.includes("standard_charge")) - .map((c) => - parseSepField(c).filter((w) => !!w && !excludeSegments.includes(w)) - ) - .filter((f) => f.length === 2) - .map((f) => f.join("|")) - .filter((c) => !!c) - ) - ).map((v) => v.split("|")) -} - -function validateRequiredField( - row: { [key: string]: string }, - field: string, - rowIndex: number, - columnIndex: number, - suffix = `` -): CsvValidationError[] { - if (!(row[field] || "").trim()) { - return [ - csvErr(rowIndex, columnIndex, field, ERRORS.REQUIRED(field, suffix)), - ] - } - return [] -} - -function validateRequiredFloatField( - row: { [key: string]: string }, - field: string, - rowIndex: number, - columnIndex: number, - suffix = `` -): CsvValidationError[] { - if (!/\d+(\.\d+)?/g.test(row[field] || "")) { - return [ - csvErr( - rowIndex, - columnIndex, - field, - ERRORS.POSITIVE_NUMBER(field, suffix) - ), - ] - } - return [] -} - -// TODO: Better way of getting this? -/** @private */ -export function isTall(columns: string[]): boolean { - return ["payer_name", "plan_name"].every((c) => columns.includes(c)) -} - -export const CsvValidatorOneOne = { - validateHeader, - validateColumns, - validateRow, - isTall, -} diff --git a/src/versions/1.1/json.ts b/src/versions/1.1/json.ts deleted file mode 100644 index fbd1656..0000000 --- a/src/versions/1.1/json.ts +++ /dev/null @@ -1,271 +0,0 @@ -import Ajv, { ErrorObject } from "ajv" -import addFormats from "ajv-formats" -import { JSONParser } from "@streamparser/json" - -import { - JsonValidatorOptions, - STATE_CODES, - ValidationError, - ValidationResult, -} from "../../types.js" -import { - BILLING_CODE_TYPES, - CHARGE_SETTINGS, - DRUG_UNITS, - CONTRACTING_METHODS, -} from "./types.js" -import { errorObjectToValidationError, parseJson } from "../common/json.js" - -const STANDARD_CHARGE_DEFINITIONS = { - billing_code_information: { - type: "object", - properties: { - code: { type: "string" }, - type: { - enum: BILLING_CODE_TYPES, - type: "string", - }, - }, - required: ["code", "type"], - }, - drug_information: { - type: "object", - properties: { - unit: { type: "string" }, - type: { enum: DRUG_UNITS, type: "string" }, - }, - required: ["unit", "type"], - }, - standard_charges: { - type: "object", - properties: { - minimum: { type: "number", exclusiveMinimum: 0 }, - maximum: { type: "number", exclusiveMinimum: 0 }, - gross_charge: { type: "number", exclusiveMinimum: 0 }, - discounted_cash: { type: "number", exclusiveMinimum: 0 }, - setting: { - enum: CHARGE_SETTINGS, - type: "string", - }, - modifiers: { - type: "array", - items: { type: "string" }, - uniqueItems: true, - }, - payers_information: { - type: "array", - items: { $ref: "#/definitions/payers_information" }, - minItems: 1, - }, - additional_generic_notes: { type: "string" }, - }, - required: ["setting"], - }, - standard_charge_information: { - type: "object", - properties: { - description: { type: "string" }, - drug_information: { $ref: "#/definitions/drug_information" }, - billing_code_information: { - type: "array", - items: { $ref: "#/definitions/billing_code_information" }, - minItems: 1, - }, - standard_charges: { - type: "array", - items: { $ref: "#/definitions/standard_charges" }, - minItems: 1, - }, - }, - required: ["description", "billing_code_information", "standard_charges"], - }, - payers_information: { - type: "object", - properties: { - 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, - 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"] }, - }, -} - -const STANDARD_CHARGE_PROPERTIES = { - type: "object", - properties: { - description: { type: "string" }, - drug_information: { $ref: "#/definitions/drug_information" }, - billing_code_information: { - type: "array", - items: { $ref: "#/definitions/billing_code_information" }, - minItems: 1, - }, - standard_charges: { - type: "array", - items: { $ref: "#/definitions/standard_charges" }, - minItems: 1, - }, - }, - required: ["description", "billing_code_information", "standard_charges"], -} - -export const STANDARD_CHARGE_SCHEMA = { - $schema: "http://json-schema.org/draft-07/schema#", - definitions: STANDARD_CHARGE_DEFINITIONS, - ...STANDARD_CHARGE_PROPERTIES, -} - -export const METADATA_DEFINITIONS = { - license_information: { - type: "object", - properties: { - license_number: { type: "string" }, - state: { - enum: STATE_CODES, - type: "string", - }, - }, - required: ["license_number", "state"], - }, -} - -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, - }, - version: { type: "string" }, - hospital_location: { type: "string" }, -} - -export const METADATA_REQUIRED = ["hospital_name", "last_updated_on", "version"] - -export const METADATA_SCHEMA = { - $schema: "http://json-schema.org/draft-07/schema#", - definitions: METADATA_DEFINITIONS, - type: "object", - properties: METADATA_PROPERTIES, - required: METADATA_REQUIRED, -} - -export const JSON_SCHEMA = { - $schema: "http://json-schema.org/draft-07/schema#", - definitions: { - ...METADATA_DEFINITIONS, - ...STANDARD_CHARGE_DEFINITIONS, - standard_charge_information: STANDARD_CHARGE_PROPERTIES, - }, - type: "object", - properties: { - ...METADATA_PROPERTIES, - standard_charge_information: { - type: "array", - items: { $ref: "#/definitions/standard_charge_information" }, - minItems: 1, - }, - }, - required: [...METADATA_REQUIRED, "standard_charge_information"], -} - -export async function validateJson( - jsonInput: File | NodeJS.ReadableStream, - options: JsonValidatorOptions = {} -): Promise { - const validator = new Ajv({ allErrors: true }) - addFormats(validator) - const parser = new JSONParser({ - paths: ["$.*", "$.standard_charge_information.*"], - keepStack: false, - }) - const metadata: { [key: string]: any } = {} - let valid = true - let hasCharges = false - const errors: ValidationError[] = [] - - return new Promise(async (resolve, reject) => { - // TODO: currently this is still storing the full array of items in "parent", but we - // would need to override some internals to get around that - parser.onValue = ({ value, key, stack }) => { - if (stack.length > 2 || key === "standard_charge_information") return - if (typeof key === "string") { - metadata[key] = value - } else { - hasCharges = true - if (!validator.validate(STANDARD_CHARGE_SCHEMA, value)) { - valid = false - errors.push( - ...(validator.errors as ErrorObject[]) - .map(errorObjectToValidationError) - .map((error) => ({ - ...error, - path: error.path.replace(/\/0\//gi, `/${key}/`), - })) - ) - } - if (options.onValueCallback && value != null) { - options.onValueCallback(value) - } - if ( - options.maxErrors && - options.maxErrors > 0 && - errors.length > options.maxErrors - ) { - resolve({ - valid: false, - errors: errors.slice(0, options.maxErrors), - }) - parser.end() - } - } - } - - parser.onEnd = () => { - // If no charges present, use the full schema to throw error for missing - if ( - !validator.validate( - hasCharges ? METADATA_SCHEMA : JSON_SCHEMA, - metadata - ) - ) { - valid = false - errors.push( - ...(validator.errors as ErrorObject[]).map( - errorObjectToValidationError - ) - ) - } - resolve({ - valid, - errors: - options.maxErrors && options.maxErrors > 0 - ? errors.slice(0, options.maxErrors) - : errors, - }) - } - - parser.onError = (e) => reject(e) - - // TODO: Assuming this must be awaited? - await parseJson(jsonInput, parser) - }) -} - -export const JsonValidatorOneOne = { - validateJson, -} diff --git a/src/versions/2.0/csv.ts b/src/versions/2.0/csv.ts deleted file mode 100644 index 95a1136..0000000 --- a/src/versions/2.0/csv.ts +++ /dev/null @@ -1,1332 +0,0 @@ -import { CsvValidationError, StateCode, STATE_CODES } from "../../types.js" -import { - csvErr, - sepColumnsEqual, - parseSepField, - getCodeCount, - isValidDate, - matchesString, - objectFromKeysValues, -} from "../common/csv.js" -import { - BILLING_CODE_TYPES, - CHARGE_SETTINGS, - STANDARD_CHARGE_METHODOLOGY, - DRUG_UNITS, -} from "./types.js" - -const 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." - -// headers must all be non-empty -export const HEADER_COLUMNS = [ - "hospital_name", // string - "last_updated_on", // date - "version", // string - maybe one of the known versions? - "hospital_location", // string - "hospital_address", // string - "license_number | [state]", // string, check for valid postal code in header - AFFIRMATION, // "true" or "false" -] as const - -export const BASE_COLUMNS = [ - "description", // non-empty string - "setting", // one of CHARGE_SETTINGS - "drug_unit_of_measurement", // positive number or blank - "drug_type_of_measurement", // one of DRUG_UNITS or blank - "modifiers", // string - "standard_charge | gross", // positive number or blank - "standard_charge | discounted_cash", // positive number or blank - "standard_charge | min", // positive number or blank - "standard_charge | max", // positive number or blank - "additional_generic_notes", // string -] - -export const TALL_COLUMNS = [ - "payer_name", // string - "plan_name", // string - "standard_charge | negotiated_dollar", // positive number or blank - "standard_charge | negotiated_percentage", // positive number or blank - "standard_charge | negotiated_algorithm", // string - "standard_charge | methodology", // one of CONTRACTING_METHODS or blank - "estimated_amount", // positive number or blank -] - -export const NEW_2025_COLUMNS = [ - "estimated_amount", - "drug_unit_of_measurement", - "drug_type_of_measurement", - "modifiers", -] - -const ERRORS = { - HEADER_COLUMN_MISSING: (column: string) => - `Header column "${column}" is miscoded or missing. You must include this header and confirm that it is encoded as specified in the data dictionary.`, - HEADER_COLUMN_BLANK: (column: string) => - `A value is required for "${column}". You must encode the missing information.`, - HEADER_STATE_CODE: (stateCode: string) => - `${stateCode} is not an allowed value for state abbreviation. You must fill in the state or territory abbreviation even if there is no license number to encode. See the table found here for the list of valid values for state and territory abbreviations https://github.com/CMSgov/hospital-price-transparency/blob/master/documentation/CSV/state_codes.md`, - DUPLICATE_HEADER_COLUMN: (column: string) => - `Column ${column} duplicated in header. You must review and revise your column headers so that each header appears only once in the first row.`, - COLUMN_MISSING: (column: string) => - `Column ${column} is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary.`, - DUPLICATE_COLUMN: (column: string) => - `Column ${column} duplicated in header. You must review and revise your column headers so that each header appears only once in the third row.`, - ALLOWED_VALUES: ( - column: string, - value: string, - allowedValues: readonly string[] - ) => - `"${column}" value "${value}" is not one of the allowed valid values. You must encode one of these valid values: ${allowedValues.join( - ", " - )}`, - INVALID_DATE: (column: string, value: string) => - `"${column}" value "${value}" is not in a valid format. You must encode the date using the ISO 8601 format: YYYY-MM-DD or the month/day/year format: MM/DD/YYYY, M/D/YYYY`, - INVALID_NUMBER: (column: string, value: string) => - `"${column}" value "${value}" is not a positive number. You must encode a positive, non-zero, numeric value.`, - POSITIVE_NUMBER: (column: string, value: string) => - `"${column}" value "${value}" is not a positive number. You must encode a positive, non-zero, numeric value.`, - CHARGE_ONE_REQUIRED: (column: string) => { - const fieldName = column.replace(" | percent", "") - return `One of "${fieldName}" or "${fieldName} | percent" is required` - }, - CODE_ONE_REQUIRED: () => { - return "If a standard charge is encoded, there must be a corresponding code and code type pairing. The code and code type pairing do not need to be in the first code and code type columns (i.e., code|1 and code|1|type)." - }, - REQUIRED: (column: string, suffix = ``) => - `A value is required for "${column}"${suffix}. You must encode the missing information.`, - ONE_OF_REQUIRED: (columns: string[], suffix = "") => - `at least one of ${columns - .map((column) => `"${column}"`) - .join(", ")} is required${suffix}`, - DOLLAR_MIN_MAX: () => - 'If there is a "payer specific negotiated charge" encoded as a dollar amount, there must be a corresponding valid value encoded for the deidentified minimum and deidentified maximum negotiated charge data.', - PERCENTAGE_ALGORITHM_ESTIMATE: () => - 'If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, then a corresponding "Estimated Allowed Amount" must also be encoded.', - NDC_DRUG_MEASURE: () => - "If code type is NDC, then the corresponding drug unit of measure and drug type of measure data element must be encoded.", - MODIFIER_EXTRA_INFO: () => - "If a modifier is encoded without an item or service, then a description and one of the following is the minimum information required: additional_payer_notes, standard_charge | negotiated_dollar, standard_charge | negotiated_percentage, or standard_charge | negotiated_algorithm.", - OTHER_METHODOLOGY_NOTES: () => - 'If the "standard charge methodology" encoded value is "other", there must be a corresponding explanation found in the "additional notes" for the associated payer-specific negotiated charge.', - ITEM_REQUIRES_CHARGE: () => - 'If an item or service is encoded, a corresponding valid value must be encoded for at least one of the following: "Gross Charge", "Discounted Cash Price", "Payer-Specific Negotiated Charge: Dollar Amount", "Payer-Specific Negotiated Charge: Percentage", "Payer-Specific Negotiated Charge: Algorithm".', - AMBIGUOUS_FORMAT: () => - "Required payer-specific information data element headers are missing or miscoded from the MRF that does not follow the specifications for the CSV “Tall” or CSV “Wide” format.", -} - -/** @private */ -export function validateHeader( - columns: string[], - row: string[] -): CsvValidationError[] { - const { errors: headerErrors, columns: headerColumns } = - validateHeaderColumns(columns) - const rowErrors = validateHeaderRow(headerColumns, row) - return [...headerErrors, ...rowErrors] -} - -/** @private */ -export function validateHeaderColumns(columns: string[]): { - errors: CsvValidationError[] - columns: string[] -} { - const rowIndex = 0 - const remainingColumns = [...HEADER_COLUMNS] - const discoveredColumns: string[] = [] - const errors: CsvValidationError[] = [] - columns.forEach((column, index) => { - const matchingColumnIndex = remainingColumns.findIndex((requiredColumn) => { - if (requiredColumn === "license_number | [state]") { - // make a best guess as to when a header is meant to be the license_number header - // if it has two parts, and the first part matches, then the second part ought to be valid - const splitColumn = column.split("|").map((v) => v.trim()) - if (splitColumn.length !== 2) { - return false - } - if (sepColumnsEqual(splitColumn[0], "license_number")) { - if (STATE_CODES.includes(splitColumn[1].toUpperCase() as StateCode)) { - return true - } else { - errors.push( - csvErr( - rowIndex, - index, - requiredColumn, - ERRORS.HEADER_STATE_CODE(splitColumn[1]) - ) - ) - return false - } - } else { - return false - } - } else { - return sepColumnsEqual(column, requiredColumn) - } - }) - if (matchingColumnIndex > -1) { - discoveredColumns[index] = column - remainingColumns.splice(matchingColumnIndex, 1) - } else { - // if we already found this column, it's a duplicate - const existingColumn = discoveredColumns.find((discovered) => { - return discovered != null && sepColumnsEqual(discovered, column) - }) - if (existingColumn) { - errors.push( - csvErr( - rowIndex, - index, - "column", - ERRORS.DUPLICATE_HEADER_COLUMN(column) - ) - ) - } - } - }) - return { - errors: [ - ...errors, - ...remainingColumns.map((requiredColumn) => { - return csvErr( - rowIndex, - -1, - requiredColumn, - ERRORS.HEADER_COLUMN_MISSING(requiredColumn) - ) - }), - ], - columns: discoveredColumns, - } -} - -/** @private */ -export function validateHeaderRow( - headers: string[], - row: string[] -): CsvValidationError[] { - const errors: CsvValidationError[] = [] - const rowIndex = 1 - headers.forEach((header, index) => { - // we aren't checking the license_number value. - if (/^license_number\s*\|\s*.{2}$/.test(header)) { - return - } - - if (header != null) { - const value = row[index]?.trim() ?? "" - if (!value) { - errors.push( - csvErr(rowIndex, index, header, ERRORS.HEADER_COLUMN_BLANK(header)) - ) - } else if ( - sepColumnsEqual(header, "last_updated_on") && - !isValidDate(value) - ) { - errors.push( - csvErr(rowIndex, index, header, ERRORS.INVALID_DATE(header, value)) - ) - } else if (sepColumnsEqual(header, AFFIRMATION)) { - errors.push( - ...validateRequiredEnumField( - objectFromKeysValues(headers, row), - header, - rowIndex, - index, - ["true", "false"] - ) - ) - } - } - }) - - return errors -} - -/** @private */ -export function validateColumns(columns: string[]): CsvValidationError[] { - const rowIndex = 2 - - if (isAmbiguousFormat(columns)) { - return [csvErr(rowIndex, -1, "", ERRORS.AMBIGUOUS_FORMAT())] - } - - const tall = isTall(columns) - const baseColumns = getBaseColumns(columns) - const remainingColumns = baseColumns.concat( - tall ? getTallColumns() : getWideColumns(columns) - ) - const enforce2025 = new Date().getFullYear() >= 2025 - const discoveredColumns: string[] = [] - const duplicateErrors: CsvValidationError[] = [] - - columns.forEach((column, index) => { - const matchingColumnIndex = remainingColumns.findIndex((requiredColumn) => - sepColumnsEqual(column, requiredColumn) - ) - if (matchingColumnIndex > -1) { - discoveredColumns[index] = column - remainingColumns.splice(matchingColumnIndex, 1) - } else { - const existingColumn = discoveredColumns.find((discovered) => { - return discovered != null && sepColumnsEqual(discovered, column) - }) - if (existingColumn) { - duplicateErrors.push( - csvErr(rowIndex, index, "column", ERRORS.DUPLICATE_COLUMN(column)) - ) - } else { - // Even if the column isn't part of the calculated remainingColumns, we still want to add it to the discoveredColumns array - discoveredColumns[index] = column - } - } - }) - - return [ - ...duplicateErrors, - ...remainingColumns.map((requiredColumn) => { - const problem = csvErr( - rowIndex, - -1, - requiredColumn, - ERRORS.COLUMN_MISSING(requiredColumn) - ) - if ( - !enforce2025 && - (NEW_2025_COLUMNS.includes(requiredColumn) || - requiredColumn.startsWith("estimated_amount |")) - ) { - problem.warning = true - } - return problem - }), - ] -} - -/** @private */ -export function validateRow( - row: { [key: string]: string }, - index: number, - columns: string[], - wide = false -): CsvValidationError[] { - const errors: CsvValidationError[] = [] - // Some columns and conditional checks have date-dependent enforcement. - const enforce2025 = new Date().getFullYear() >= 2025 - - const requiredFields = ["description"] - requiredFields.forEach((field) => - errors.push( - ...validateRequiredField(row, field, index, columns.indexOf(field)) - ) - ) - - // check code and code-type columns - const codeColumns = columns.filter((column) => { - return /^code \| \d+$/.test(column) - }) - let foundCode = false - codeColumns.forEach((codeColumn) => { - const codeTypeColumn = `${codeColumn} | type` - // if the type column is missing, we already created an error when checking the columns - // if both columns exist, we can check the values - if (row[codeTypeColumn] != null) { - const trimCode = row[codeColumn].trim() - const trimType = row[codeTypeColumn].trim() - if (trimCode.length === 0 && trimType.length > 0) { - foundCode = true - errors.push( - csvErr( - index, - columns.indexOf(codeColumn), - codeColumn, - ERRORS.REQUIRED(codeColumn) - ) - ) - } else if (trimCode.length > 0 && trimType.length === 0) { - foundCode = true - errors.push( - csvErr( - index, - columns.indexOf(codeTypeColumn), - codeTypeColumn, - ERRORS.REQUIRED(codeTypeColumn) - ) - ) - } else if (trimCode.length > 0 && trimType.length > 0) { - foundCode = true - } - errors.push( - ...validateOptionalEnumField( - row, - codeTypeColumn, - index, - columns.indexOf(codeTypeColumn), - BILLING_CODE_TYPES - ) - ) - } - }) - - const modifierPresent = (row["modifiers"] || "").trim().length > 0 - if (modifierPresent && !foundCode) { - errors.push(...validateModifierRow(row, index, columns, wide)) - return errors - } - - if (!foundCode) { - errors.push( - csvErr(index, columns.length, "code | 1", ERRORS.CODE_ONE_REQUIRED()) - ) - } - - errors.push( - ...validateRequiredEnumField( - row, - "setting", - index, - columns.indexOf("setting"), - CHARGE_SETTINGS - ) - ) - - if ( - (row["drug_unit_of_measurement"] || "").trim() || - (row["drug_type_of_measurement"] || "").trim() - ) { - errors.push( - ...validateRequiredFloatField( - row, - "drug_unit_of_measurement", - index, - columns.indexOf("drug_unit_of_measurement"), - ' when "drug_type_of_measurement" is present' - ).map((err) => (enforce2025 ? err : { ...err, warning: true })) - ) - errors.push( - ...validateRequiredEnumField( - row, - "drug_type_of_measurement", - index, - columns.indexOf("drug_type_of_measurement"), - DRUG_UNITS, - ' when "drug_unit_of_measurement" is present' - ).map((err) => (enforce2025 ? err : { ...err, warning: true })) - ) - } - - const chargeFields = [ - "standard_charge | gross", - "standard_charge | discounted_cash", - "standard_charge | min", - "standard_charge | max", - ] - chargeFields.forEach((field) => - errors.push( - ...validateOptionalFloatField(row, field, index, columns.indexOf(field)) - ) - ) - - // If code type is NDC, then the corresponding drug unit of measure and - // drug type of measure data elements must be encoded. Required beginning 1/1/2025. - const allCodeTypes = columns - .filter((column) => { - return /^code \| \d+ | type$/.test(column) - }) - .map((codeTypeColumn) => row[codeTypeColumn]) - if (allCodeTypes.some((codeType) => matchesString(codeType, "NDC"))) { - const invalidFields = [ - "drug_unit_of_measurement", - "drug_type_of_measurement", - ].filter((field) => { - return ( - validateRequiredField(row, field, index, columns.indexOf(field)) - .length > 0 - ) - }) - if (invalidFields.length > 0) { - errors.push( - csvErr( - index, - columns.indexOf(invalidFields[0]), - invalidFields[0], - ERRORS.NDC_DRUG_MEASURE(), - !enforce2025 - ) - ) - } - } - - if (wide) { - errors.push(...validateWideFields(row, index, columns, foundCode)) - } else { - errors.push(...validateTallFields(row, index, columns, foundCode)) - } - - return errors -} - -/** @private */ -function validateModifierRow( - row: { [key: string]: string }, - index: number, - columns: string[], - wide: boolean -): CsvValidationError[] { - const errors: CsvValidationError[] = [] - // If a modifier is encoded without an item or service, then a description and one of the following - // is the minimum information required: - // additional_generic_notes, additional_payer_notes, standard_charge | negotiated_dollar, standard_charge | negotiated_percentage, or standard_charge | negotiated_algorithm - const enforce2025 = new Date().getFullYear() >= 2025 - if (wide) { - const payersPlans = getPayersPlans(columns) - const payersPlansColumns: string[] = payersPlans - .flatMap((payerPlan) => [ - ["standard_charge", ...payerPlan, "negotiated_dollar"], - ["standard_charge", ...payerPlan, "negotiated_percentage"], - ["standard_charge", ...payerPlan, "negotiated_algorithm"], - ["additional_payer_notes", ...payerPlan], - ]) - .map((c) => c.join(" | ")) - const modifierRequiredFields = [ - "additional_generic_notes", - ...payersPlansColumns, - ] - if ( - validateOneOfRequiredField( - row, - modifierRequiredFields, - index, - columns.indexOf(modifierRequiredFields[0]), - " for wide format when a modifier is encoded without an item or service" - ).length > 0 - ) { - errors.push( - csvErr( - index, - columns.indexOf(modifierRequiredFields[0]), - modifierRequiredFields[0], - ERRORS.MODIFIER_EXTRA_INFO() - ) - ) - } - } else { - const modifierRequiredFields = [ - "additional_generic_notes", - "standard_charge | negotiated_dollar", - "standard_charge | negotiated_percentage", - "standard_charge | negotiated_algorithm", - ] - if ( - validateOneOfRequiredField( - row, - modifierRequiredFields, - index, - columns.indexOf(modifierRequiredFields[0]), - " for tall format when a modifier is encoded without an item or service" - ).length > 0 - ) { - errors.push( - csvErr( - index, - columns.indexOf(modifierRequiredFields[0]), - modifierRequiredFields[0], - ERRORS.MODIFIER_EXTRA_INFO() - ) - ) - } - } - - // other conditionals don't apply for modifier rows, but any values entered should still be the correct type - errors.push( - ...validateOptionalEnumField( - row, - "setting", - index, - columns.indexOf("setting"), - CHARGE_SETTINGS - ) - ) - - errors.push( - ...validateOptionalFloatField( - row, - "drug_unit_of_measurement", - index, - columns.indexOf("drug_unit_of_measurement") - ).map((err) => (enforce2025 ? err : { ...err, warning: true })) - ) - errors.push( - ...validateOptionalEnumField( - row, - "drug_type_of_measurement", - index, - columns.indexOf("drug_type_of_measurement"), - DRUG_UNITS - ).map((err) => (enforce2025 ? err : { ...err, warning: true })) - ) - - const chargeFields = [ - "standard_charge | gross", - "standard_charge | discounted_cash", - "standard_charge | min", - "standard_charge | max", - ] - chargeFields.forEach((field) => - errors.push( - ...validateOptionalFloatField(row, field, index, columns.indexOf(field)) - ) - ) - - if (wide) { - errors.push(...validateWideModifierFields(row, index, columns)) - } else { - errors.push(...validateTallModifierFields(row, index, columns)) - } - - return errors -} - -/** @private */ -export function validateWideFields( - row: { [key: string]: string }, - index: number, - columns: string[], - foundCode: boolean -): CsvValidationError[] { - const errors: CsvValidationError[] = [] - - // Some conditional checks have date-dependent enforcement. - const enforceConditionals = new Date().getFullYear() >= 2025 - const payersPlans = getPayersPlans(columns) - - // If a "payer specific negotiated charge" is encoded as a dollar amount, percentage, or algorithm - // then a corresponding valid value for the payer name, plan name, and standard charge methodology - // must also be encoded. - payersPlans.forEach(([payer, plan]) => { - if ( - ( - row[`standard_charge | ${payer} | ${plan} | negotiated_dollar`] || "" - ).trim().length > 0 || - ( - row[`standard_charge | ${payer} | ${plan} | negotiated_percentage`] || - "" - ).trim().length > 0 || - ( - row[`standard_charge | ${payer} | ${plan} | negotiated_algorithm`] || "" - ).trim().length > 0 - ) { - errors.push( - ...validateRequiredEnumField( - row, - `standard_charge | ${payer} | ${plan} | methodology`, - index, - columns.indexOf(`standard_charge | ${payer} | ${plan} | methodology`), - STANDARD_CHARGE_METHODOLOGY, - " when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm" - ) - ) - } else { - // if it's not required, check that the value is valid - errors.push( - ...validateOptionalEnumField( - row, - `standard_charge | ${payer} | ${plan} | methodology`, - index, - columns.indexOf(`standard_charge | ${payer} | ${plan} | methodology`), - STANDARD_CHARGE_METHODOLOGY - ) - ) - } - }) - - // If the "standard charge methodology" encoded value is "other", there must be a - // corresponding explanation found in the "additional notes" for the associated - // payer-specific negotiated charge. - payersPlans.forEach(([payer, plan]) => { - if ( - (row[`standard_charge | ${payer} | ${plan} | methodology`] || "") - .trim() - .match("other") - ) { - if ( - validateRequiredField( - row, - `additional_payer_notes | ${payer} | ${plan}`, - index, - columns.indexOf(`additional_payer_notes | ${payer} | ${plan}`) - ).length > 0 - ) { - errors.push( - csvErr( - index, - columns.indexOf(`additional_payer_notes | ${payer} | ${plan}`), - `additional_payer_notes | ${payer} | ${plan}`, - ERRORS.OTHER_METHODOLOGY_NOTES() - ) - ) - } - } - }) - - // If an item or service is encoded, a corresponding valid value must be encoded for - // at least one of the following: "Gross Charge", "Discounted Cash Price", - // "Payer-Specific Negotiated Charge: Dollar Amount", "Payer-Specific Negotiated Charge: Percentage", - // "Payer-Specific Negotiated Charge: Algorithm". - if (foundCode) { - const payerPlanChargeColumns = payersPlans - .flatMap((payerPlan) => [ - ["standard_charge", ...payerPlan, "negotiated_dollar"], - ["standard_charge", ...payerPlan, "negotiated_percentage"], - ["standard_charge", ...payerPlan, "negotiated_algorithm"], - ]) - .map((c) => c.join(" | ")) - const chargeColumns = [ - "standard_charge | gross", - "standard_charge | discounted_cash", - ...payerPlanChargeColumns, - ] - const itemHasCharge = - validateOneOfRequiredField( - row, - chargeColumns, - index, - columns.indexOf(chargeColumns[0]) - ).length === 0 - if (!itemHasCharge) { - errors.push( - csvErr( - index, - columns.indexOf(chargeColumns[0]), - chargeColumns[0], - ERRORS.ITEM_REQUIRES_CHARGE() - ) - ) - } - } - - // If there is a "payer specific negotiated charge" encoded as a dollar amount, - // there must be a corresponding valid value encoded for the deidentified minimum and deidentified maximum negotiated charge data. - const dollarChargeColumns = columns.filter((column) => - column.endsWith("| negotiated_dollar") - ) - if (dollarChargeColumns.some((column) => row[column].trim().length > 0)) { - const invalidFields = [ - "standard_charge | min", - "standard_charge | max", - ].filter((field) => { - return ( - validateRequiredField(row, field, index, columns.indexOf(field)) - .length > 0 - ) - }) - if (invalidFields.length > 0) { - errors.push( - csvErr( - index, - columns.indexOf(invalidFields[0]), - invalidFields[0], - ERRORS.DOLLAR_MIN_MAX() - ) - ) - } - } - - // If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, - // then a corresponding "Estimated Allowed Amount" must also be encoded. Required beginning 1/1/2025. - - payersPlans.forEach(([payer, plan]) => { - if ( - ( - row[`standard_charge | ${payer} | ${plan} | negotiated_dollar`] || "" - ).trim().length === 0 && - (( - row[`standard_charge | ${payer} | ${plan} | negotiated_percentage`] || - "" - ).trim().length > 0 || - ( - row[`standard_charge | ${payer} | ${plan} | negotiated_algorithm`] || - "" - ).trim().length > 0) - ) { - const estimatedField = `estimated_amount | ${payer} | ${plan}` - if ( - validateRequiredFloatField( - row, - estimatedField, - index, - columns.indexOf(estimatedField) - ).length > 0 - ) { - errors.push( - csvErr( - index, - columns.indexOf(estimatedField), - estimatedField, - ERRORS.PERCENTAGE_ALGORITHM_ESTIMATE(), - !enforceConditionals - ) - ) - } - } - }) - - // Ensuring that the numeric values are greater than zero. - payersPlans.forEach(([payer, plan]) => { - if ( - ( - row[`standard_charge | ${payer} | ${plan} | negotiated_dollar`] || "" - ).trim().length > 0 && - validateRequiredFloatField( - row, - `standard_charge | ${payer} | ${plan} | negotiated_dollar`, - index, - columns.indexOf( - `standard_charge | ${payer} | ${plan} | negotiated_dollar` - ) - ).length > 0 - ) { - errors.push( - csvErr( - index, - columns.indexOf( - `standard_charge | ${payer} | ${plan} | negotiated_dollar` - ), - `standard_charge | ${payer} | ${plan} | negotiated_dollar`, - ERRORS.INVALID_NUMBER( - `standard_charge | ${payer} | ${plan} | negotiated_dollar`, - row[`standard_charge | ${payer} | ${plan} | negotiated_dollar`] - ) - ) - ) - } - - if ( - ( - row[`standard_charge | ${payer} | ${plan} | negotiated_percentage`] || - "" - ).trim().length > 0 && - validateRequiredFloatField( - row, - `standard_charge | ${payer} | ${plan} | negotiated_percentage`, - index, - columns.indexOf( - `standard_charge | ${payer} | ${plan} | negotiated_percentage` - ) - ).length > 0 - ) { - errors.push( - csvErr( - index, - columns.indexOf( - `standard_charge | ${payer} | ${plan} | negotiated_percentage` - ), - `standard_charge | ${payer} | ${plan} | negotiated_percentage`, - ERRORS.INVALID_NUMBER( - `standard_charge | ${payer} | ${plan} | negotiated_percentage`, - row[`standard_charge | ${payer} | ${plan} | negotiated_percentage`] - ) - ) - ) - } - - if ( - (row[`estimated_amount | ${payer} | ${plan}`] || "").trim().length > 0 && - validateRequiredFloatField( - row, - `estimated_amount | ${payer} | ${plan}`, - index, - columns.indexOf(`estimated_amount | ${payer} | ${plan}`) - ).length > 0 - ) { - errors.push( - csvErr( - index, - columns.indexOf(`estimated_amount | ${payer} | ${plan}`), - `estimated_amount | ${payer} | ${plan}`, - ERRORS.INVALID_NUMBER( - `estimated_amount | ${payer} | ${plan}`, - row[`estimated_amount | ${payer} | ${plan}`] - ), - !enforceConditionals - ) - ) - } - }) - return errors -} - -/** @private */ -// checks the same fields as validateWideFields, but they are now optional -export function validateWideModifierFields( - row: { [key: string]: string }, - index: number, - columns: string[] -): CsvValidationError[] { - const errors: CsvValidationError[] = [] - - const payersPlans = getPayersPlans(columns) - const floatChargeFields = payersPlans - .flatMap((payerPlan) => [ - ["standard_charge", ...payerPlan, "negotiated_dollar"], - ["standard_charge", ...payerPlan, "negotiated_percentage"], - ]) - .map((c) => c.join(" | ")) - floatChargeFields.forEach((field) => { - errors.push( - ...validateOptionalFloatField(row, field, index, columns.indexOf(field)) - ) - }) - - const methodologyFields = payersPlans.map((payerPlan) => - ["standard_charge", ...payerPlan, "methodology"].join(" | ") - ) - methodologyFields.forEach((field) => { - errors.push( - ...validateOptionalEnumField( - row, - field, - index, - columns.indexOf(field), - STANDARD_CHARGE_METHODOLOGY - ) - ) - }) - - return errors -} - -/** @private */ -export function validateTallFields( - row: { [key: string]: string }, - index: number, - columns: string[], - foundCode: boolean -): CsvValidationError[] { - const errors: CsvValidationError[] = [] - const enforce2025 = new Date().getFullYear() >= 2025 - // first, some type checks - const floatChargeFields = [ - "standard_charge | negotiated_dollar", - "standard_charge | negotiated_percentage", - ] - floatChargeFields.forEach((field) => { - errors.push( - ...validateOptionalFloatField(row, field, index, columns.indexOf(field)) - ) - }) - - // Conditional checks are here. Some have date-dependent enforcement. - const enforceConditionals = new Date().getFullYear() >= 2025 - - // If a "payer specific negotiated charge" is encoded as a dollar amount, percentage, or algorithm - // then a corresponding valid value for the payer name, plan name, and standard charge methodology - // must also be encoded. - if ( - (row["standard_charge | negotiated_dollar"] || "").trim().length > 0 || - (row["standard_charge | negotiated_percentage"] || "").trim().length > 0 || - (row["standard_charge | negotiated_algorithm"] || "").trim().length > 0 - ) { - errors.push( - ...validateRequiredField( - row, - "payer_name", - index, - columns.indexOf("payer_name"), - " when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm" - ) - ) - - errors.push( - ...validateRequiredField( - row, - "plan_name", - index, - columns.indexOf("plan_name"), - " when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm" - ) - ) - - errors.push( - ...validateRequiredEnumField( - row, - "standard_charge | methodology", - index, - columns.indexOf("standard_charge | methodology"), - STANDARD_CHARGE_METHODOLOGY, - " when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm" - ) - ) - } - - //If the "standard charge methodology" encoded value is "other", there must be a - // corresponding explanation found in the "additional notes" for the associated - // payer-specific negotiated charge. - if ( - matchesString( - (row["standard_charge | methodology"] || "").trim(), - "other" - ) && - !(row["additional_generic_notes"] || "").trim() - ) { - errors.push( - csvErr( - index, - columns.indexOf("additional_generic_notes"), - "additional_generic_notes", - ERRORS.OTHER_METHODOLOGY_NOTES() - ) - ) - } - - // If an item or service is encoded, a corresponding valid value must be encoded for at least one of the following: - // "Gross Charge", "Discounted Cash Price", "Payer-Specific Negotiated Charge: Dollar Amount", - // "Payer-Specific Negotiated Charge: Percentage", "Payer-Specific Negotiated Charge: Algorithm". - - if (foundCode) { - const itemHasCharge = - validateOneOfRequiredField( - row, - [ - "standard_charge | gross", - "standard_charge | discounted_cash", - "standard_charge | negotiated_dollar", - "standard_charge | negotiated_percentage", - "standard_charge | negotiated_algorithm", - ], - index, - columns.indexOf("standard_charge | gross") - ).length === 0 - if (!itemHasCharge) { - errors.push( - csvErr( - index, - columns.indexOf("standard_charge | gross"), - "standard_charge | gross", - ERRORS.ITEM_REQUIRES_CHARGE() - ) - ) - } - } - - // If there is a "payer specific negotiated charge" encoded as a dollar amount, - // there must be a corresponding valid value encoded for the deidentified minimum and deidentified maximum negotiated charge data. - // min and max have already been checked for valid float format, so this checks only if they are present. - if ((row["standard_charge | negotiated_dollar"] || "").trim().length > 0) { - const invalidFields = [ - "standard_charge | min", - "standard_charge | max", - ].filter((field) => { - return ( - validateRequiredField(row, field, index, columns.indexOf(field)) - .length > 0 - ) - }) - if (invalidFields.length > 0) { - errors.push( - csvErr( - index, - columns.indexOf(invalidFields[0]), - invalidFields[0], - ERRORS.DOLLAR_MIN_MAX() - ) - ) - } - } - - // If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, - // then a corresponding "Estimated Allowed Amount" must also be encoded. Required beginning 1/1/2025. - if ( - (row["standard_charge | negotiated_dollar"] || "").trim().length === 0 && - ((row["standard_charge | negotiated_percentage"] || "").trim().length > 0 || - (row["standard_charge | negotiated_algorithm"] || "").trim().length > 0) - ) { - const estimatedField = `estimated_amount` - if ( - validateRequiredFloatField( - row, - estimatedField, - index, - columns.indexOf(estimatedField) - ).length > 0 - ) { - errors.push( - csvErr( - index, - columns.indexOf(estimatedField), - estimatedField, - ERRORS.PERCENTAGE_ALGORITHM_ESTIMATE(), - !enforceConditionals - ) - ) - } - } else { - errors.push( - ...validateOptionalFloatField( - row, - "estimated_amount", - index, - columns.indexOf("estimated_amount") - ).map((err) => (enforce2025 ? err : { ...err, warning: true })) - ) - } - - return errors -} - -/** @private */ -// checks the same fields as validateTallFields, but they are now optional -export function validateTallModifierFields( - row: { [key: string]: string }, - index: number, - columns: string[] -): CsvValidationError[] { - const errors: CsvValidationError[] = [] - - const floatChargeFields = [ - "standard_charge | negotiated_dollar", - "standard_charge | negotiated_percentage", - ] - floatChargeFields.forEach((field) => { - errors.push( - ...validateOptionalFloatField(row, field, index, columns.indexOf(field)) - ) - }) - - errors.push( - ...validateOptionalEnumField( - row, - "standard_charge | methodology", - index, - columns.indexOf("standard_charge | methodology"), - STANDARD_CHARGE_METHODOLOGY - ) - ) - - return errors -} - -/** @private */ -export function getBaseColumns(columns: string[]): string[] { - const codeCount = Math.max(1, getCodeCount(columns)) - const codeColumns = Array(codeCount) - .fill(0) - .flatMap((_, i) => [`code | ${i + 1}`, `code | ${i + 1} | type`]) - - return [...BASE_COLUMNS, ...codeColumns] -} - -/** @private */ -export function getWideColumns(columns: string[]): string[] { - const payersPlans = getPayersPlans(columns) - const payersPlansColumns: string[] = payersPlans - .flatMap((payerPlan) => [ - ["standard_charge", ...payerPlan, "negotiated_dollar"], - ["standard_charge", ...payerPlan, "negotiated_percentage"], - ["standard_charge", ...payerPlan, "methodology"], - ["standard_charge", ...payerPlan, "negotiated_algorithm"], - ["estimated_amount", ...payerPlan], // turn into a warning - ["additional_payer_notes", ...payerPlan], - ]) - .map((c) => c.join(" | ")) - - return payersPlansColumns -} - -/** @private */ -export function getTallColumns(): string[] { - return TALL_COLUMNS -} - -function getPayersPlans(columns: string[]): string[][] { - const excludeSegments = [ - "standard_charge", - "min", - "max", - "gross", - "discounted_cash", - "negotiated_dollar", - "negotiated_percentage", - "negotiated_algorithm", - "methodology", - ] - return Array.from( - new Set( - columns - .filter((c) => c.includes("standard_charge")) - .map((c) => - parseSepField(c).filter((w) => !!w && !excludeSegments.includes(w)) - ) - .filter((f) => f.length === 2) - .map((f) => f.join("|")) - .filter((c) => !!c) - ) - ).map((v) => v.split("|")) -} - -function validateRequiredField( - row: { [key: string]: string }, - field: string, - rowIndex: number, - columnIndex: number, - suffix = `` -): CsvValidationError[] { - if (!(row[field] || "").trim()) { - return [ - csvErr(rowIndex, columnIndex, field, ERRORS.REQUIRED(field, suffix)), - ] - } - return [] -} - -function validateOneOfRequiredField( - row: { [key: string]: string }, - fields: string[], - rowIndex: number, - columnIndex: number, - suffix = "" -): CsvValidationError[] { - if ( - fields.every((field) => { - return (row[field] || "").trim().length === 0 - }) - ) { - return [ - csvErr( - rowIndex, - columnIndex, - fields[0], - ERRORS.ONE_OF_REQUIRED(fields, suffix) - ), - ] - } - return [] -} - -function validateRequiredFloatField( - row: { [key: string]: string }, - field: string, - rowIndex: number, - columnIndex: number, - suffix = "" -): CsvValidationError[] { - if (!(row[field] || "").trim()) { - return [ - csvErr(rowIndex, columnIndex, field, ERRORS.REQUIRED(field, suffix)), - ] - // We wrap the three alternative patterns (\d+\.\d+|\.\d+|\d+) within a non-capturing group (?: ... ). This group acts as a container but doesn't capture any matched text during the regex search. - } else if ( - !/^(?:\d+\.\d+|\.\d+|\d+)$/g.test(row[field].trim()) || - parseFloat(row[field].trim()) <= 0 - ) { - return [ - csvErr( - rowIndex, - columnIndex, - field, - ERRORS.POSITIVE_NUMBER(field, row[field]) - ), - ] - } - return [] -} - -function validateOptionalFloatField( - row: { [key: string]: string }, - field: string, - rowIndex: number, - columnIndex: number -): CsvValidationError[] { - if (!(row[field] || "").trim()) { - return [] - } else if ( - !/^(?:\d+\.\d+|\.\d+|\d+)$/g.test(row[field].trim()) || - parseFloat(row[field].trim()) <= 0 - ) { - return [ - csvErr( - rowIndex, - columnIndex, - field, - ERRORS.INVALID_NUMBER(field, row[field]) - ), - ] - } - return [] -} - -function validateRequiredEnumField( - row: { [key: string]: string }, - field: string, - rowIndex: number, - columnIndex: number, - allowedValues: readonly string[], - suffix = "" -) { - if (!(row[field] || "").trim()) { - return [ - csvErr(rowIndex, columnIndex, field, ERRORS.REQUIRED(field, suffix)), - ] - } else { - if (!allowedValues.some((allowed) => matchesString(row[field], allowed))) { - return [ - csvErr( - rowIndex, - columnIndex, - field, - ERRORS.ALLOWED_VALUES(field, row[field], allowedValues) - ), - ] - } - } - return [] -} - -function validateOptionalEnumField( - row: { [key: string]: string }, - field: string, - rowIndex: number, - columnIndex: number, - allowedValues: readonly string[] -) { - if (!(row[field] || "").trim()) { - return [] - } else { - if (!allowedValues.some((allowed) => matchesString(row[field], allowed))) { - return [ - csvErr( - rowIndex, - columnIndex, - field, - ERRORS.ALLOWED_VALUES(field, row[field], allowedValues) - ), - ] - } - } - - return [] -} - -// TODO: Better way of getting this? -/** @private */ -export function isTall(columns: string[]): boolean { - return ["payer_name", "plan_name"].every((c) => columns.includes(c)) -} - -export function isAmbiguousFormat(columns: string[]): boolean { - const tall = isTall(columns) - const wide = getPayersPlans(columns) - if ((!tall && wide.length === 0) || (tall && wide.length > 0)) { - return true - } - return false -} - -export const CsvValidatorTwoZero = { - validateHeader, - validateColumns, - validateRow, - isTall, - isAmbiguousFormat, -} diff --git a/src/versions/2.0/json.ts b/src/versions/2.0/json.ts deleted file mode 100644 index 37a5b86..0000000 --- a/src/versions/2.0/json.ts +++ /dev/null @@ -1,537 +0,0 @@ -import Ajv, { ErrorObject } from "ajv" -import addFormats from "ajv-formats" -import { JSONParser } from "@streamparser/json" - -import { - JsonValidatorOptions, - STATE_CODES, - ValidationError, - ValidationResult, -} from "../../types.js" -import { - BILLING_CODE_TYPES, - CHARGE_SETTINGS, - DRUG_UNITS, - STANDARD_CHARGE_METHODOLOGY, -} from "./types.js" -import { errorObjectToValidationError, parseJson } from "../common/json.js" -import { addErrorsToList } from "../../utils.js" - -const STANDARD_CHARGE_DEFINITIONS = { - code_information: { - type: "object", - properties: { - code: { type: "string", minLength: 1 }, - type: { - enum: BILLING_CODE_TYPES, - type: "string", - }, - }, - required: ["code", "type"], - }, - drug_information: { - type: "object", - properties: { - unit: { type: "string", minLength: 1 }, - type: { enum: DRUG_UNITS, type: "string" }, - }, - required: ["unit", "type"], - }, - - standard_charges: { - type: "object", - properties: { - minimum: { type: "number", exclusiveMinimum: 0 }, - maximum: { type: "number", exclusiveMinimum: 0 }, - gross_charge: { type: "number", exclusiveMinimum: 0 }, - discounted_cash: { type: "number", exclusiveMinimum: 0 }, - setting: { - enum: CHARGE_SETTINGS, - type: "string", - }, - payers_information: { - type: "array", - items: { $ref: "#/definitions/payers_information" }, - minItems: 1, - }, - additional_generic_notes: { type: "string" }, - }, - required: ["setting"], - anyOf: [ - { type: "object", required: ["gross_charge"] }, - { type: "object", required: ["discounted_cash"] }, - { - type: "object", - properties: { - payers_information: { - type: "array", - items: { - anyOf: [ - { type: "object", required: ["standard_charge_dollar"] }, - { type: "object", required: ["standard_charge_algorithm"] }, - { type: "object", required: ["standard_charge_percentage"] }, - ], - }, - }, - }, - required: ["payers_information"], - }, - ], - if: { - type: "object", - properties: { - payers_information: { - type: "array", - contains: { - type: "object", - required: ["standard_charge_dollar"], - }, - }, - }, - required: ["payers_information"], - }, - then: { - required: ["minimum", "maximum"], - }, - }, - standard_charge_information: { - type: "object", - properties: { - description: { type: "string", minLength: 1 }, - drug_information: { $ref: "#/definitions/drug_information" }, - code_information: { - type: "array", - items: { $ref: "#/definitions/code_information" }, - minItems: 1, - }, - standard_charges: { - type: "array", - items: { $ref: "#/definitions/standard_charges" }, - minItems: 1, - }, - }, - required: ["description", "code_information", "standard_charges"], - if: { - type: "object", - properties: { - code_information: { - type: "array", - contains: { - type: "object", - properties: { - type: { - const: "NDC", - }, - }, - }, - }, - }, - }, - then: { - required: ["drug_information"], - }, - }, - payers_information: { - type: "object", - properties: { - 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" }, - 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", "methodology"], - allOf: [ - { - if: { - properties: { - methodology: { - const: "other", - }, - }, - required: ["methodology"], - }, - then: { - properties: { - additional_payer_notes: { type: "string", minLength: 1 }, - }, - required: ["additional_payer_notes"], - }, - }, - { - if: { - anyOf: [ - { required: ["standard_charge_percentage"] }, - { required: ["standard_charge_algorithm"] }, - ], - not: { - required: ["standard_charge_dollar"], - }, - }, - then: { - required: ["estimated_amount"], // Required beginning 1/1/2025 - }, - }, - ], - }, -} - -const STANDARD_CHARGE_PROPERTIES = { - type: "object", - properties: { - description: { type: "string", minLength: 1 }, - drug_information: { $ref: "#/definitions/drug_information" }, - code_information: { - type: "array", - items: { $ref: "#/definitions/code_information" }, - minItems: 1, - }, - standard_charges: { - type: "array", - items: { $ref: "#/definitions/standard_charges" }, - minItems: 1, - }, - }, - required: ["description", "code_information", "standard_charges"], - - if: { - type: "object", - properties: { - code_information: { - type: "array", - contains: { - type: "object", - properties: { - type: { - const: "NDC", - }, - }, - }, - }, - }, - }, - then: { - required: ["drug_information"], - }, -} - -export const STANDARD_CHARGE_SCHEMA = { - $schema: "http://json-schema.org/draft-07/schema#", - definitions: STANDARD_CHARGE_DEFINITIONS, - ...STANDARD_CHARGE_PROPERTIES, -} - -export const METADATA_DEFINITIONS = { - license_information: { - type: "object", - properties: { - license_number: { type: "string" }, - state: { - enum: STATE_CODES, - type: "string", - }, - }, - required: ["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", - minLength: 1, - }, - code: { - type: "string", - minLength: 1, - }, - 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", - minLength: 1, - }, - plan_name: { - type: "string", - minLength: 1, - }, - description: { - type: "string", - minLength: 1, - }, - setting: { - enum: CHARGE_SETTINGS, - type: "string", - }, - }, - required: ["payer_name", "plan_name", "description"], - }, -} - -export const METADATA_PROPERTIES = { - hospital_name: { type: "string", minLength: 1 }, - last_updated_on: { type: "string", format: "date" }, - license_information: { - $ref: "#/definitions/license_information", - }, - 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", - }, - modifier_information: { - type: "array", - items: { - $ref: "#/definitions/modifier_information", - }, - }, -} - -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#", - definitions: METADATA_DEFINITIONS, - type: "object", - properties: METADATA_PROPERTIES, - required: METADATA_REQUIRED, -} - -export const JSON_SCHEMA = { - $schema: "http://json-schema.org/draft-07/schema#", - definitions: { - ...METADATA_DEFINITIONS, - ...STANDARD_CHARGE_DEFINITIONS, - standard_charge_information: STANDARD_CHARGE_PROPERTIES, - }, - type: "object", - properties: { - ...METADATA_PROPERTIES, - standard_charge_information: { - type: "array", - items: { $ref: "#/definitions/standard_charge_information" }, - minItems: 1, - }, - }, - required: [...METADATA_REQUIRED, "standard_charge_information"], -} - -export async function validateJson( - jsonInput: File | NodeJS.ReadableStream, - options: JsonValidatorOptions = {} -): Promise { - const validator = new Ajv({ allErrors: true }) - addFormats(validator) - const parser = new JSONParser({ - paths: [ - "$.hospital_name", - "$.last_updated_on", - "$.license_information", - "$.version", - "$.hospital_address", - "$.hospital_location", - "$.affirmation", - "$.modifier_information", - "$.standard_charge_information.*", - ], - keepStack: false, - }) - const metadata: { [key: string]: any } = {} - let valid = true - let hasCharges = false - const errors: ValidationError[] = [] - const enforce2025 = new Date().getFullYear() >= 2025 - const counts = { - errors: 0, - warnings: 0, - } - - return new Promise(async (resolve) => { - // TODO: currently this is still storing the full array of items in "parent", but we - // would need to override some internals to get around that - parser.onValue = ({ value, key, stack }) => { - 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? - hasCharges = true - if (!validator.validate(STANDARD_CHARGE_SCHEMA, value)) { - const validationErrors = (validator.errors as ErrorObject[]) - .map( - enforce2025 - ? errorObjectToValidationError - : errorObjectToValidationErrorWithWarnings - ) - .map((error) => { - const pathPrefix = stack - .filter((se) => se.key) - .map((se) => se.key) - .join("/") - return { - ...error, - path: `/${pathPrefix}/${key}${error.path}`, - } - }) - addErrorsToList(validationErrors, errors, options.maxErrors, counts) - valid = counts.errors === 0 - } - if (options.onValueCallback && value != null) { - options.onValueCallback(value) - } - if ( - options.maxErrors && - options.maxErrors > 0 && - counts.errors >= options.maxErrors - ) { - resolve({ - valid: false, - errors: errors, - }) - parser.end() - } - } - } - - parser.onEnd = () => { - // If no charges present, use the full schema to throw error for missing - if ( - !validator.validate( - hasCharges ? METADATA_SCHEMA : JSON_SCHEMA, - metadata - ) - ) { - const validationErrors = (validator.errors as ErrorObject[]).map( - enforce2025 - ? errorObjectToValidationError - : errorObjectToValidationErrorWithWarnings - ) - addErrorsToList(validationErrors, errors, options.maxErrors, counts) - valid = counts.errors === 0 - } - resolve({ - valid, - errors, - }) - } - - parser.onError = (e) => { - parser.onEnd = () => null - parser.onError = () => null - parser.end() - resolve({ - valid: false, - errors: [ - { - path: "", - message: `JSON parsing error: ${e.message}. The validator is unable to review a syntactically invalid JSON file. Please ensure that your file is well-formatted JSON.`, - }, - ], - }) - } - - parseJson(jsonInput, parser) - }) -} - -export const JsonValidatorTwoZero = { - validateJson, -} - -function errorObjectToValidationErrorWithWarnings( - error: ErrorObject, - index: number, - errors: ErrorObject[] -): ValidationError { - const validationError = errorObjectToValidationError(error) - // If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, - // then a corresponding "Estimated Allowed Amount" must also be encoded. Required beginning 1/1/2025. - // two validation errors occur for this conditional: one for the "required" keyword, one for the "if" keyword - if ( - error.schemaPath === - "#/definitions/payers_information/allOf/1/then/required" - ) { - validationError.warning = true - } else if ( - error.schemaPath === "#/definitions/payers_information/allOf/1/if" && - index > 0 && - errors[index - 1].schemaPath === - "#/definitions/payers_information/allOf/1/then/required" - ) { - validationError.warning = true - } - // If code type is NDC, then the corresponding drug unit of measure and drug type of measure data elements - // must be encoded. Required beginning 1/1/2025. - // two validation errors occur for this conditional: one for the "required" keyword, one for the "if" keyword - else if ( - error.schemaPath === "#/then/required" && - error.params.missingProperty === "drug_information" - ) { - validationError.warning = true - } else if ( - error.schemaPath === "#/if" && - index > 0 && - errors[index - 1].schemaPath === "#/then/required" && - errors[index - 1].params.missingProperty === "drug_information" - ) { - validationError.warning = true - } - // Any error involving the properties that are new for 2025 are warnings. - // These properties are: drug_information, modifier_information, estimated_amount - else if ( - error.instancePath.includes("/drug_information") || - error.instancePath.includes("/modifier_information") || - error.instancePath.includes("/estimated_amount") - ) { - validationError.warning = true - } - return validationError -} diff --git a/src/versions/common/csv.ts b/src/versions/common/csv.ts deleted file mode 100644 index 5cd1557..0000000 --- a/src/versions/common/csv.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { CsvValidationError, ValidationError } from "../../types.js" - -export function csvErrorToValidationError( - err: CsvValidationError -): ValidationError { - return { - path: csvCellName(err.row, err.column), - field: err.field, - message: err.message, - ...(err.warning ? { warning: err.warning } : {}), - } -} - -// Helper to reduce boilerplate -export function csvErr( - row: number, - column: number, - field: string | undefined, - message: string, - warning?: boolean -): CsvValidationError { - return { row, column, field, message, warning } -} - -export function cleanColumnNames(columns: string[]) { - return columns.map((col) => - col - .split("|") - .map((v) => v.trim()) - .join(" | ") - ) -} - -export function sepColumnsEqual(colA: string, colB: string) { - const cleanA = colA.split("|").map((v) => v.trim().toUpperCase()) - const cleanB = colB.split("|").map((v) => v.trim().toUpperCase()) - return ( - cleanA.length === cleanB.length && - cleanA.every((a, idx: number) => a === cleanB[idx]) - ) -} - -export const ASCII_UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - -export function csvCellName(row: number, column: number): string { - return `${(column ?? -1) >= 0 ? csvColumnName(column) : "row "}${row + 1}` -} - -export function csvColumnName(column: number): string { - let name = "" - while (column >= 0) { - name = ASCII_UPPERCASE[column % ASCII_UPPERCASE.length] + name - column = Math.floor(column / ASCII_UPPERCASE.length) - 1 - } - return name -} - -export function objectFromKeysValues( - keys: string[], - values: string[] -): { [key: string]: string } { - return Object.fromEntries( - keys.map((key, index) => [key, values[index]]).filter((entry) => entry) - ) -} - -export function rowIsEmpty(row: string[]): boolean { - return row.every((value) => !value.trim()) -} - -export function parseSepField(field: string): string[] { - return field.split("|").map((v) => v.trim()) -} - -export function getCodeCount(columns: string[]): number { - return Math.max( - 0, - ...columns - .map((c) => - c - .split("|") - .map((v) => v.trim()) - .filter((v) => !!v) - ) - .filter( - (c) => - c[0] === "code" && - (c.length === 2 || (c.length === 3 && c[2] === "type")) - ) - .map((c) => +c[1].replace(/\D/g, "")) - .filter((v) => !isNaN(v)) - ) -} - -export function isValidDate(value: string) { - // required format is YYYY-MM-DD or MM/DD/YYYY or M/D/YYYY or MM/D/YYYY or M/DD/YYYY - //const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/) - const dateMatch1 = value.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/) - const dateMatch2 = value.match(/^(\d{4})-(\d{2})-(\d{2})$/) - - if (dateMatch1 != null) { - // UTC methods are used because "date-only forms are interpreted as a UTC time", - // as per https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format - // check that the parsed date matches the input, to guard against e.g. February 31 - const matchYear = dateMatch1[3] - const matchMonth = dateMatch1[1] - const matchDate = dateMatch1[2] - const expectedYear = parseInt(matchYear) - const expectedMonth = parseInt(matchMonth) - 1 - const expectedDate = parseInt(matchDate) - const parsedDate = new Date(value) - return ( - expectedYear === parsedDate.getUTCFullYear() && - expectedMonth === parsedDate.getUTCMonth() && - expectedDate === parsedDate.getUTCDate() - ) - } else if (dateMatch2 != null) { - const matchYear = dateMatch2[1] - const matchMonth = dateMatch2[2] - const matchDate = dateMatch2[3] - const expectedYear = parseInt(matchYear) - const expectedMonth = parseInt(matchMonth) - 1 - const expectedDate = parseInt(matchDate) - const parsedDate = new Date(value) - return ( - expectedYear === parsedDate.getUTCFullYear() && - expectedMonth === parsedDate.getUTCMonth() && - expectedDate === parsedDate.getUTCDate() - ) - } - return false -} - -export function matchesString(value: string, target: string) { - if (typeof value !== "string") return false - return value.trim().toLocaleUpperCase() === target.trim().toLocaleUpperCase() -} diff --git a/src/versions/common/json.ts b/src/versions/common/json.ts deleted file mode 100644 index f975c35..0000000 --- a/src/versions/common/json.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ErrorObject } from "ajv" -import { ValidationError } from "../../types.js" -import { JSONParser } from "@streamparser/json" -import { removeBOM } from "../../utils.js" - -export async function parseJson( - jsonInput: File | NodeJS.ReadableStream, - parser: JSONParser -): Promise { - if (typeof window !== "undefined" && jsonInput instanceof File) { - const stream = jsonInput.stream() - const reader = stream.getReader() - const textDecoder = new TextDecoder("utf-8") - - function readChunk() { - return reader.read().then(({ done, value }) => { - if (done) { - parser.end() - return - } - - parser.write(textDecoder.decode(value)) - readChunk() - }) - } - - readChunk() - } else { - let firstChunk = true - const jsonStream = jsonInput as NodeJS.ReadableStream - jsonStream.on("end", () => parser.end()) - jsonStream.on("error", (e) => { - throw e - }) - jsonStream.on("data", (data: string) => { - // strip utf-8 BOM: see https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8 - if (firstChunk) { - data = removeBOM(data) - firstChunk = false - } - parser.write(data) - }) - } -} - -export function errorObjectToValidationError( - error: ErrorObject -): ValidationError { - return { - path: error.instancePath, - field: error.instancePath.split("/").pop(), - message: error.message as string, - } -} diff --git a/test/1.1/csv.spec.ts b/test/1.1/csv.spec.ts deleted file mode 100644 index 7b2614b..0000000 --- a/test/1.1/csv.spec.ts +++ /dev/null @@ -1,295 +0,0 @@ -import test from "ava" -import { - validateHeaderColumns, - validateHeaderRow, - validateColumns, - validateTallFields, - validateWideFields, - getBaseColumns, - getTallColumns, - isTall, - BASE_COLUMNS, - MIN_MAX_COLUMNS, - HEADER_COLUMNS, - TALL_COLUMNS, -} from "../../src/versions/1.1/csv.js" -import { CONTRACTING_METHODS } from "../../src/versions/1.1/types.js" - -const VALID_HEADER_COLUMNS = HEADER_COLUMNS.map((c) => - c === "license_number | state" ? "license_number | md" : c -) - -test("validateHeaderColumns", (t) => { - const emptyResult = validateHeaderColumns([]) - t.is(emptyResult.errors.length, HEADER_COLUMNS.length) - t.is(emptyResult.columns.length, 0) - const basicResult = validateHeaderColumns(VALID_HEADER_COLUMNS) - t.is(basicResult.errors.length, 0) - t.deepEqual(basicResult.columns, VALID_HEADER_COLUMNS) - const reversedColumns = [...VALID_HEADER_COLUMNS].reverse() - const reverseResult = validateHeaderColumns(reversedColumns) - t.is(reverseResult.errors.length, 0) - t.deepEqual(reverseResult.columns, reversedColumns) - const extraColumns = [ - "extra1", - ...VALID_HEADER_COLUMNS.slice(0, 2), - "extra2", - ...VALID_HEADER_COLUMNS.slice(2), - ] - const extraResult = validateHeaderColumns(extraColumns) - t.is(extraResult.errors.length, 0) - t.deepEqual(extraResult.columns, [ - undefined, - ...VALID_HEADER_COLUMNS.slice(0, 2), - undefined, - ...VALID_HEADER_COLUMNS.slice(2), - ]) -}) - -test("validateHeaderRow", (t) => { - t.is( - validateHeaderRow(VALID_HEADER_COLUMNS, []).length, - VALID_HEADER_COLUMNS.length - ) - t.is( - validateHeaderRow(VALID_HEADER_COLUMNS, [ - "name", - "2022-01-01", - "1.0.0", - "Woodlawn", - "Aid", - "001 | MD", - ]).length, - 0 - ) - const extraColumns = [ - undefined, - ...VALID_HEADER_COLUMNS.slice(0, 2), - undefined, - ...VALID_HEADER_COLUMNS.slice(2), - ] - t.is( - validateHeaderRow(extraColumns, [ - "", - "name", - "2022-01-01", - "", - "1.0.0", - "Woodlawn", - "Aid", - "001 | MD", - ]).length, - 0 - ) - t.assert( - validateHeaderRow(VALID_HEADER_COLUMNS, [ - "", - "2022-01-01", - "1.0.0", - "Woodlawn", - "Aid", - "001 | MD", - ])[0].message.includes("blank") - ) -}) - -test("validateColumns tall", (t) => { - t.is( - validateColumns([ - ...getBaseColumns([ - "code | 1", - "code| 1 | type", - "code | 2", - "code | 2 | type", - ]), - ...getTallColumns([]), - ]).length, - 0 - ) - // Extra columns should be ignored - t.is( - validateColumns([ - ...getBaseColumns([ - "code | 1", - "code | 1 |type", - "code | 2", - "code | 2 | type", - ]), - ...getTallColumns([]), - "test", - ]).length, - 0 - ) - const baseCols = getBaseColumns([ - "code | 1", - "code | 1 | type", - "code | 2", - "code | 2 | type", - ]) - t.is( - validateColumns([ - ...baseCols.slice(0, baseCols.length - 1), - ...getTallColumns([]), - "test", - ]).length, - 1 - ) -}) - -test("validateColumns wide", (t) => { - const columns = [ - ...BASE_COLUMNS, - ...MIN_MAX_COLUMNS, - "code | 1", - "code | 1 | type", - "standard_charge | Payer | Plan", - "standard_charge | Payer | Plan | percent", - "standard_charge | Payer | Plan | contracting_method", - "additional_payer_notes | Payer | Plan", - "additional_generic_notes", - ] - t.is(validateColumns(columns).length, 0) - // any order is fine - const reverseColumns = [...columns].reverse() - t.is(validateColumns(reverseColumns).length, 0) - // extra payer and plan are fine - t.is( - validateColumns([ - ...columns, - "standard_charge | Payer | Plan 2", - "standard_charge | Payer | Plan 2 | percent", - "standard_charge | Payer | Plan 2 | contracting_method", - "additional_payer_notes | Payer | Plan 2", - "standard_charge | Another Payer | Plan", - "standard_charge | Another Payer | Plan | percent", - "standard_charge | Another Payer | Plan | contracting_method", - "additional_payer_notes | Another Payer | Plan", - ]).length, - 0 - ) - // missing percent is an error - t.is( - validateColumns([ - ...columns, - "standard_charge | Payer | Plan 2", - "standard_charge | Payer | Plan 2 | contracting_method", - "additional_payer_notes | Payer | Plan 2", - ]).length, - 1 - ) - // missing contracting_method is an error - t.is( - validateColumns([ - ...columns, - "standard_charge | Payer | Plan 2", - "standard_charge | Payer | Plan 2 | percent", - "additional_payer_notes | Payer | Plan 2", - ]).length, - 1 - ) - // missing additional_payer_notes is an error - t.is( - validateColumns([ - ...columns, - "standard_charge | Payer | Plan 2", - "standard_charge | Payer | Plan 2 | percent", - "standard_charge | Payer | Plan 2 | contracting_method", - ]).length, - 1 - ) -}) - -test("isTall", (t) => { - t.assert(isTall([...BASE_COLUMNS, ...MIN_MAX_COLUMNS, ...TALL_COLUMNS])) - t.assert( - isTall([ - ...BASE_COLUMNS, - ...MIN_MAX_COLUMNS, - ...TALL_COLUMNS.slice(0, TALL_COLUMNS.length - 1), - "test", - ]) - ) - t.assert( - !isTall([ - ...BASE_COLUMNS, - "code | 1", - "code | 1 | type", - "code | 2", - "code | 2| type", - ...MIN_MAX_COLUMNS, - ...TALL_COLUMNS.filter((c) => c !== "payer_name"), - ]) - ) - t.assert( - !isTall([ - ...BASE_COLUMNS, - ...MIN_MAX_COLUMNS, - "standard_charge | payer | plan", - "standard_charge |payer | plan | percent", - "standard_charge | payer | plan | contracting_method", - "standard_charge | payer | plan", - "standard_charge | payer | plan | percent", - "additional_generic_notes", - ]) - ) -}) - -test("validateTallFields", (t) => { - t.is( - validateTallFields( - { - payer_name: "Payer", - plan_name: "Plan", - "standard_charge | negotiated_dollar": "1.0", - "standard_charge | negotiated_percent": "", - "standard_charge | contracting_method": CONTRACTING_METHODS[0], - additional_generic_notes: "", - }, - 0 - ).length, - 0 - ) - t.is( - validateTallFields( - { - payer_name: "Payer", - plan_name: "Plan", - "standard_charge | negotiated_dollar": "", - "standard_charge | negotiated_percent": "", - "standard_charge | contracting_method": CONTRACTING_METHODS[0], - additional_generic_notes: "", - }, - 0 - ).length, - 2 - ) -}) - -test("validateWideFields", (t) => { - t.is( - validateWideFields( - { - "standard_charge | payer | plan": "", - "standard_charge | payer | plan | percent": "1.5", - "standard_charge | payer | plan | contracting_method": - CONTRACTING_METHODS[2], - additional_generic_notes: "", - }, - 0 - ).length, - 0 - ) - t.is( - validateWideFields( - { - "standard_charge | payer | plan": "", - "standard_charge | payer | plan | percent": "", - "standard_charge | payer | plan | contracting_method": - CONTRACTING_METHODS[2], - additional_generic_notes: "", - }, - 0 - ).length, - 1 - ) -}) diff --git a/test/1.1/json.spec.ts b/test/1.1/json.spec.ts deleted file mode 100644 index 0ac43a2..0000000 --- a/test/1.1/json.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import test from "ava" -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" - ) - t.is(result.valid, false) - t.is(result.errors.length, 1) -}) - -test("validateJson empty", async (t) => { - const result = await validateJson( - loadFixtureStream("/1.1/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("/1.1/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("/1.1/sample-valid.json"), - "v1.1" - ) - t.is(result.valid, true) - t.deepEqual(result.errors.length, 0) -}) diff --git a/test/2.0/csv.e2e.spec.ts b/test/2.0/csv.e2e.spec.ts deleted file mode 100644 index 8948bc1..0000000 --- a/test/2.0/csv.e2e.spec.ts +++ /dev/null @@ -1,169 +0,0 @@ -import test from "ava" -import { validateCsv } from "../../src/csv.js" -import { loadFixtureStream } from "../utils.js" - -test("validateCsvTall", async (t) => { - const result = await validateCsv( - loadFixtureStream("/2.0/sample-tall-valid.csv"), - "v2.0" - ) - t.is(result.valid, true) - t.deepEqual(result.errors.length, 0) -}) - -test("validateCsvTall quoted column name", async (t) => { - // this test shows correct behavior when a file contains a BOM and the first column name is quoted - const result = await validateCsv( - loadFixtureStream("/2.0/sample-tall-valid-quoted.csv"), - "v2.0" - ) - t.is(result.valid, true) - t.deepEqual(result.errors.length, 0) -}) - -test("validateCsvWide", async (t) => { - const result = await validateCsv( - loadFixtureStream("/2.0/sample-wide-valid.csv"), - "v2.0" - ) - t.is(result.valid, true) - t.deepEqual(result.errors.length, 0) -}) - -test("validateCsvWideHeaderError", async (t) => { - const result = await validateCsv( - loadFixtureStream("/2.0/sample-wide-error-header.csv"), - "v2.0" - ) - t.is(result.valid, false) - t.deepEqual(result.errors, [ - { - path: "row 1", - field: "hospital_location", - message: - 'Header column "hospital_location" is miscoded or missing. You must include this header and confirm that it is encoded as specified in the data dictionary.', - }, - { - path: "G2", - field: - "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.", - message: - '"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." value "yes" is not one of the allowed valid values. You must encode one of these valid values: true, false', - }, - { - path: "A1", - message: - "Errors were found in the headers or values in rows 1 through 3, so the remaining rows were not evaluated.", - }, - ]) -}) - -test("validateCsvHeaderEmpty", async (t) => { - const result = await validateCsv( - loadFixtureStream("/2.0/sample-wide-header-empty.csv"), - "v2.0" - ) - t.is(result.valid, false) - t.is(result.errors.length, 7) -}) - -test("validateCsvWideMissingValueError", async (t) => { - const result = await validateCsv( - loadFixtureStream("/2.0/sample-wide-error-missing-value.csv"), - "v2.0" - ) - t.is(result.valid, false) - t.deepEqual(result.errors, [ - { - path: "B4", - field: "code | 1", - message: - 'A value is required for "code | 1". You must encode the missing information.', - }, - ]) -}) - -test("validateCsvWideMissingEnumError", async (t) => { - const result = await validateCsv( - loadFixtureStream("/2.0/sample-wide-error-bad-enum.csv"), - "v2.0" - ) - t.is(result.valid, false) - t.deepEqual(result.errors, [ - { - path: "C4", - field: "code | 1 | type", - message: - '"code | 1 | type" value "ms-drgg" is not one of the allowed valid values. You must encode one of these valid values: CPT, HCPCS, ICD, DRG, MS-DRG, R-DRG, S-DRG, APS-DRG, AP-DRG, APR-DRG, APC, NDC, HIPPS, LOCAL, EAPG, CDT, RC, CDM, TRIS-DRG', - }, - ]) -}) - -test("validateCsvWideMissingMissingRequiredColumnError", async (t) => { - const result = await validateCsv( - loadFixtureStream("/2.0/sample-wide-error-header-standardcharge.csv"), - "v2.0" - ) - t.is(result.valid, false) - t.deepEqual(result.errors, [ - { - path: "row 3", - field: - "standard_charge | platform_health_insurance | ppo | negotiated_dollar", - message: - "Column standard_charge | platform_health_insurance | ppo | negotiated_dollar is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary.", - }, - { - path: "A1", - message: - "Errors were found in the headers or values in rows 1 through 3, so the remaining rows were not evaluated.", - }, - ]) -}) - -test("validate columns with date-dependent enforcement", async (t) => { - const result = await validateCsv( - loadFixtureStream("/2.0/sample-wide-missing-new-columns.csv"), - "v2.0" - ) - const enforce2025 = new Date().getFullYear() >= 2025 - if (enforce2025) { - t.is(result.valid, false) - t.deepEqual(result.errors, [ - { - path: "row 3", - field: "drug_unit_of_measurement", - message: - "Column drug_unit_of_measurement is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary.", - }, - { - path: "row 3", - field: "drug_type_of_measurement", - message: - "Column drug_type_of_measurement is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary.", - }, - { - path: "A1", - message: "Errors were seen in headers so rows were not evaluated", - }, - ]) - } else { - t.is(result.valid, true) - t.deepEqual(result.errors, [ - { - path: "row 3", - field: "drug_unit_of_measurement", - message: - "Column drug_unit_of_measurement is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary.", - warning: true, - }, - { - path: "row 3", - field: "drug_type_of_measurement", - message: - "Column drug_type_of_measurement is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary.", - warning: true, - }, - ]) - } -}) diff --git a/test/2.0/csv.spec.ts b/test/2.0/csv.spec.ts deleted file mode 100644 index 3affff9..0000000 --- a/test/2.0/csv.spec.ts +++ /dev/null @@ -1,1763 +0,0 @@ -import test from "ava" -import { - validateHeaderColumns, - validateHeaderRow, - validateColumns, - validateRow, - isAmbiguousFormat, - HEADER_COLUMNS, - BASE_COLUMNS, - TALL_COLUMNS, - NEW_2025_COLUMNS, -} from "../../src/versions/2.0/csv.js" - -const VALID_HEADER_COLUMNS = HEADER_COLUMNS.map((c) => - c === "license_number | [state]" ? "license_number | MD" : c -) - -test("validateHeaderColumns", (t) => { - const emptyResult = validateHeaderColumns([]) - t.is(emptyResult.errors.length, HEADER_COLUMNS.length) - t.is(emptyResult.columns.length, 0) - t.is( - emptyResult.errors[0].message, - 'Header column "hospital_name" is miscoded or missing. You must include this header and confirm that it is encoded as specified in the data dictionary.' - ) - const enforce2025 = new Date().getFullYear() >= 2025 - emptyResult.errors.forEach((err) => { - if (NEW_2025_COLUMNS.includes(err.field || "")) { - t.assert(!!err.warning === enforce2025) - } else { - t.assert(err.warning == null) - } - }) - - const basicResult = validateHeaderColumns(VALID_HEADER_COLUMNS) - t.is(basicResult.errors.length, 0) - t.deepEqual(basicResult.columns, VALID_HEADER_COLUMNS) - const reversedColumns = [...VALID_HEADER_COLUMNS].reverse() - const reverseResult = validateHeaderColumns(reversedColumns) - t.is(reverseResult.errors.length, 0) - t.deepEqual(reverseResult.columns, reversedColumns) - const extraColumns = [ - "extra1", - ...VALID_HEADER_COLUMNS.slice(0, 2), - "extra2", - ...VALID_HEADER_COLUMNS.slice(2), - ] - const extraResult = validateHeaderColumns(extraColumns) - t.is(extraResult.errors.length, 0) - t.deepEqual(extraResult.columns, [ - undefined, - ...VALID_HEADER_COLUMNS.slice(0, 2), - undefined, - ...VALID_HEADER_COLUMNS.slice(2), - ]) - const duplicateColumns = [...VALID_HEADER_COLUMNS, "hospital_location"] - const duplicateResult = validateHeaderColumns(duplicateColumns) - t.is(duplicateResult.errors.length, 1) - t.is( - duplicateResult.errors[0].message, - "Column hospital_location duplicated in header. You must review and revise your column headers so that each header appears only once in the first row." - ) - t.deepEqual(duplicateResult.columns, VALID_HEADER_COLUMNS) - const invalidStateColumns = HEADER_COLUMNS.map((c) => - c === "license_number | [state]" ? "license_number | ZZ" : c - ) - const invalidStateErrors = validateHeaderColumns(invalidStateColumns) - t.is(invalidStateErrors.errors.length, 2) - t.assert( - invalidStateErrors.errors[0].message.includes( - "ZZ is not an allowed value for state abbreviation" - ) - ) -}) - -test("validateHeaderRow", (t) => { - t.is( - validateHeaderRow(VALID_HEADER_COLUMNS, []).length, - VALID_HEADER_COLUMNS.length - 1 - ) - - t.is( - validateHeaderRow(VALID_HEADER_COLUMNS, [ - "name", - "2022-01-01", - "1.0.0", - "Woodlawn", - "123 Address", - "001 | MD", - "true", - ]).length, - 0 - ) - // leading and trailing spaces are allowed, and comparisons are not case-sensitive - t.is( - validateHeaderRow(VALID_HEADER_COLUMNS, [ - "name", - "2022-01-01", - "1.0.0", - "Woodlawn", - "123 Address", - "001 | MD", - " TRUE ", - ]).length, - 0 - ) - const missingNameErrors = validateHeaderRow(VALID_HEADER_COLUMNS, [ - "", - "2022-01-01", - "1.0.0", - "Woodlawn", - "123 Address", - "001 | MD", - "true", - ]) - t.is(missingNameErrors.length, 1) - t.is( - missingNameErrors[0].message, - 'A value is required for "hospital_name". You must encode the missing information.' - ) - - const missingLicenseNumberValid = validateHeaderRow(VALID_HEADER_COLUMNS, [ - "name", - "2022-01-01", - "1.0.0", - "Woodlawn", - "123 Address", - "", - "true", - ]) - t.is(missingLicenseNumberValid.length, 0) - - // last_updated_on must be a valid date - const invalidDateResult = validateHeaderRow(VALID_HEADER_COLUMNS, [ - "name", - "2022-14-01", - "1.0.0", - "Woodlawn", - "123 Address", - "001 | MD", - "true", - ]) - t.is(invalidDateResult.length, 1) - t.is( - invalidDateResult[0].message, - '"last_updated_on" value "2022-14-01" is not in a valid format. You must encode the date using the ISO 8601 format: YYYY-MM-DD or the month/day/year format: MM/DD/YYYY, M/D/YYYY' - ) - // last_updated_on is allowed to be MM/DD/YYYY - const dateResult1 = validateHeaderRow(VALID_HEADER_COLUMNS, [ - "name", - "01/07/2024", - "1.0.0", - "Woodlawn", - "123 Address", - "001 | MD", - "true", - ]) - t.is(dateResult1.length, 0) - // last_updated_on is allowed to be M/D/YYYY - const dateResult2 = validateHeaderRow(VALID_HEADER_COLUMNS, [ - "name", - "1/7/2024", - "1.0.0", - "Woodlawn", - "123 Address", - "001 | MD", - "true", - ]) - t.is(dateResult2.length, 0) - // affirmation must be true - const wrongAffirmationResult = validateHeaderRow(VALID_HEADER_COLUMNS, [ - "name", - "2022-01-01", - "1.0.0", - "Woodlawn", - "123 Address", - "001 | MD", - "yes", - ]) - t.is(wrongAffirmationResult.length, 1) - t.is( - wrongAffirmationResult[0].message, - '"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." value "yes" is not one of the allowed valid values. You must encode one of these valid values: true, false' - ) -}) - -test("validateColumns tall", (t) => { - const columns = [ - ...BASE_COLUMNS, - ...TALL_COLUMNS, - "code | 1", - "code | 1 | type", - ] - t.is(validateColumns(columns).length, 0) - // any order is okay - const reverseColumns = [...columns].reverse() - t.is(validateColumns(reverseColumns).length, 0) - // extra code columns may appear - const extraCodes = [ - ...columns, - "code|2", - "code|2|type", - "code|3", - "code|3|type", - ] - t.is(validateColumns(extraCodes).length, 0) - // duplicates are not allowed - const duplicateColumns = [...columns, "payer_name"] - const duplicateErrors = validateColumns(duplicateColumns) - t.is(duplicateErrors.length, 1) - t.is( - duplicateErrors[0].message, - "Column payer_name duplicated in header. You must review and revise your column headers so that each header appears only once in the third row." - ) - // if a column is missing, that's an error - const missingBase = columns.slice(1) - const missingBaseResult = validateColumns(missingBase) - t.is(missingBaseResult.length, 1) - t.is( - missingBaseResult[0].message, - "Column description is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary." - ) - // this also applies to code columns, where code|i means that code|i|type must appear - const missingCode = [...columns, "code | 2"] - const missingCodeResult = validateColumns(missingCode) - t.is(missingCodeResult.length, 1) - t.is( - missingCodeResult[0].message, - "Column code | 2 | type is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary." - ) - // code|i|type means that code|i must be present - const missingType = [...columns, "code | 2 | type"] - const missingTypeResult = validateColumns(missingType) - t.is(missingTypeResult.length, 1) - t.is( - missingTypeResult[0].message, - "Column code | 2 is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary." - ) -}) - -test("isAmbiguousFormat", (t) => { - const columnsTall = [ - ...BASE_COLUMNS, - ...TALL_COLUMNS, - "code | 1", - "code | 1 | type", - "code | 2", - "code | 2 | type", - ] - - const columnsWide = [ - ...BASE_COLUMNS, - "code | 1", - "code | 1 | type", - "standard_charge | Payer One | Basic Plan | negotiated_dollar", - "standard_charge | Payer One | Basic Plan | negotiated_percentage", - "standard_charge | Payer One | Basic Plan | negotiated_algorithm", - "standard_charge | Payer One | Basic Plan | methodology", - "estimated_amount | Payer One | Basic Plan", - "additional_payer_notes | Payer One | Basic Plan", - ] - - const columnsAmbiguous1 = [...BASE_COLUMNS, "code | 1", "code | 1 | type"] - const columnsAmbiguous2 = [...columnsWide, ...TALL_COLUMNS] - - const basicResultTall = isAmbiguousFormat(columnsTall) - t.is(basicResultTall, false) - - const basicResultWide = isAmbiguousFormat(columnsWide) - t.is(basicResultWide, false) - - const basicResultAmbiguous1 = isAmbiguousFormat(columnsAmbiguous1) - t.is(basicResultAmbiguous1, true) - - const basicResultAmbiguous2 = isAmbiguousFormat(columnsAmbiguous2) - t.is(basicResultAmbiguous2, true) - - const basicResultAmbiguousError1 = validateColumns(columnsAmbiguous1) - t.is(basicResultAmbiguousError1.length, 1) - t.is( - basicResultAmbiguousError1[0].message, - "Required payer-specific information data element headers are missing or miscoded from the MRF that does not follow the specifications for the CSV “Tall” or CSV “Wide” format." - ) - // the ambiguous row error is always in row 3 of the csv - // but csv rows are 1-indexed, and error rows are 0-indexed, so we expect 2 - t.is(basicResultAmbiguousError1[0].row, 2) - - const basicResultAmbiguousError2 = validateColumns(columnsAmbiguous2) - t.is(basicResultAmbiguousError2.length, 1) - t.is( - basicResultAmbiguousError2[0].message, - "Required payer-specific information data element headers are missing or miscoded from the MRF that does not follow the specifications for the CSV “Tall” or CSV “Wide” format." - ) - t.is(basicResultAmbiguousError2[0].row, 2) -}) - -test("validateRow tall", (t) => { - const columns = [ - ...BASE_COLUMNS, - ...TALL_COLUMNS, - "code | 1", - "code | 1 | type", - "code | 2", - "code | 2 | type", - ] - const basicRow = { - description: "basic description", - setting: "inpatient", - "code | 1": "12345", - "code | 1 | type": "DRG", - "code | 2": "", - "code | 2 | type": "", - drug_unit_of_measurement: "8.5", - drug_type_of_measurement: "ML", - modifiers: "", - "standard_charge | gross": "100", - "standard_charge | discounted_cash": "200.50", - "standard_charge | min": "50", - "standard_charge | max": "500", - additional_generic_notes: "some notes", - payer_name: "Acme Payer", - plan_name: "Acme Basic Coverage", - "standard_charge | negotiated_dollar": "300", - "standard_charge | negotiated_percentage": "", - "standard_charge | negotiated_algorithm": "", - "standard_charge | methodology": "fee schedule", - estimated_amount: "", - } - const enforce2025 = new Date().getFullYear() >= 2025 - const basicResult = validateRow(basicRow, 5, columns, false) - t.is(basicResult.length, 0) - // description must not be empty - const noDescriptionRow = { ...basicRow, description: "" } - const noDescriptionResult = validateRow(noDescriptionRow, 6, columns, false) - t.is(noDescriptionResult.length, 1) - t.is( - noDescriptionResult[0].message, - 'A value is required for "description". You must encode the missing information.' - ) - // setting must not be empty - const noSettingRow = { ...basicRow, setting: "" } - const noSettingResult = validateRow(noSettingRow, 7, columns, false) - t.is(noSettingResult.length, 1) - t.is( - noSettingResult[0].message, - 'A value is required for "setting". You must encode the missing information.' - ) - // setting must be one of CHARGE_SETTINGS - const wrongSettingRow = { ...basicRow, setting: "everywhere" } - const wrongSettingResult = validateRow(wrongSettingRow, 8, columns, false) - t.is(wrongSettingResult.length, 1) - t.is( - wrongSettingResult[0].message, - '"setting" value "everywhere" is not one of the allowed valid values. You must encode one of these valid values: inpatient, outpatient, both' - ) - // drug_unit_of_measurement must be positive number if present - const emptyDrugUnitRow = { ...basicRow, drug_unit_of_measurement: "" } - const emptyDrugUnitResult = validateRow(emptyDrugUnitRow, 9, columns, false) - t.is(emptyDrugUnitResult.length, 1) - t.is( - emptyDrugUnitResult[0].message, - 'A value is required for "drug_unit_of_measurement" when "drug_type_of_measurement" is present. You must encode the missing information.' - ) - t.assert(emptyDrugUnitResult[0].warning === !enforce2025) - const wrongDrugUnitRow = { ...basicRow, drug_unit_of_measurement: "-4" } - const wrongDrugUnitResult = validateRow(wrongDrugUnitRow, 10, columns, false) - t.is(wrongDrugUnitResult.length, 1) - t.is( - wrongDrugUnitResult[0].message, - '"drug_unit_of_measurement" value "-4" is not a positive number. You must encode a positive, non-zero, numeric value.' - ) - t.assert(wrongDrugUnitResult[0].warning === !enforce2025) - // drug_type_of_measurement must be one of DRUG_UNITS if present - const emptyDrugTypeRow = { ...basicRow, drug_type_of_measurement: "" } - const emptyDrugTypeResult = validateRow(emptyDrugTypeRow, 12, columns, false) - t.is(emptyDrugTypeResult.length, 1) - t.is( - emptyDrugTypeResult[0].message, - 'A value is required for "drug_type_of_measurement" when "drug_unit_of_measurement" is present. You must encode the missing information.' - ) - t.assert(emptyDrugTypeResult[0].warning === !enforce2025) - const wrongDrugTypeRow = { ...basicRow, drug_type_of_measurement: "KG" } - const wrongDrugTypeResult = validateRow(wrongDrugTypeRow, 12, columns, false) - t.is(wrongDrugTypeResult.length, 1) - t.is( - wrongDrugTypeResult[0].message, - '"drug_type_of_measurement" value "KG" is not one of the allowed valid values. You must encode one of these valid values: GR, ME, ML, UN, F2, EA, GM' - ) - t.assert(wrongDrugTypeResult[0].warning === !enforce2025) - // standard_charge | gross must be positive number if present - const emptyGrossRow = { ...basicRow, "standard_charge | gross": "" } - const emptyGrossResult = validateRow(emptyGrossRow, 13, columns, false) - t.is(emptyGrossResult.length, 0) - const wrongGrossRow = { ...basicRow, "standard_charge | gross": "3,000" } - const wrongGrossResult = validateRow(wrongGrossRow, 14, columns, false) - t.is(wrongGrossResult.length, 1) - t.is( - wrongGrossResult[0].message, - '"standard_charge | gross" value "3,000" is not a positive number. You must encode a positive, non-zero, numeric value.' - ) - // standard_charge | discounted_cash must be positive number if present - const emptyDiscountedRow = { - ...basicRow, - "standard_charge | discounted_cash": "", - } - const emptyDiscountedResult = validateRow( - emptyDiscountedRow, - 15, - columns, - false - ) - t.is(emptyDiscountedResult.length, 0) - const wrongDiscountedRow = { - ...basicRow, - "standard_charge | discounted_cash": "300.25.1", - } - const wrongDiscountedResult = validateRow( - wrongDiscountedRow, - 16, - columns, - false - ) - t.is(wrongDiscountedResult.length, 1) - t.is( - wrongDiscountedResult[0].message, - '"standard_charge | discounted_cash" value "300.25.1" is not a positive number. You must encode a positive, non-zero, numeric value.' - ) - // standard_charge | min must be positive number if present - const emptyMinRow = { - ...basicRow, - "standard_charge | min": "", - "standard_charge | negotiated_dollar": "", - "standard_charge | negotiated_percentage": "80", - estimated_amount: "150", - } - const emptyMinResult = validateRow(emptyMinRow, 17, columns, false) - t.is(emptyMinResult.length, 0) - const wrongMinRow = { - ...basicRow, - "standard_charge | min": "-5", - } - const wrongMinResult = validateRow(wrongMinRow, 18, columns, false) - t.is(wrongMinResult.length, 1) - t.is( - wrongMinResult[0].message, - '"standard_charge | min" value "-5" is not a positive number. You must encode a positive, non-zero, numeric value.' - ) - // standard_charge | max must be positive number if present - const emptyMaxRow = { - ...basicRow, - "standard_charge | max": "", - "standard_charge | negotiated_dollar": "", - "standard_charge | negotiated_percentage": "80", - estimated_amount: "250", - } - const emptyMaxResult = validateRow(emptyMaxRow, 19, columns, false) - t.is(emptyMaxResult.length, 0) - const wrongMaxRow = { - ...basicRow, - "standard_charge | max": "-2", - } - const wrongMaxResult = validateRow(wrongMaxRow, 20, columns, false) - t.is(wrongMaxResult.length, 1) - t.is( - wrongMaxResult[0].message, - '"standard_charge | max" value "-2" is not a positive number. You must encode a positive, non-zero, numeric value.' - ) - // no code pairs is invalid - const noCodesRow = { ...basicRow, "code | 1": "", "code | 1 | type": "" } - const noCodesResult = validateRow(noCodesRow, 21, columns, false) - t.is(noCodesResult.length, 1) - t.is( - noCodesResult[0].message, - "If a standard charge is encoded, there must be a corresponding code and code type pairing. The code and code type pairing do not need to be in the first code and code type columns (i.e., code|1 and code|1|type)." - ) - // a code pair not in the first column is valid - const secondCodeRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - "code | 2": "234", - "code | 2 | type": "LOCAL", - } - const secondCodeResult = validateRow(secondCodeRow, 22, columns, false) - t.is(secondCodeResult.length, 0) - // a code without a code type is invalid - const noTypeRow = { ...basicRow, "code | 1 | type": "" } - const noTypeResult = validateRow(noTypeRow, 23, columns, false) - t.is(noTypeResult.length, 1) - t.is( - noTypeResult[0].message, - 'A value is required for "code | 1 | type". You must encode the missing information.' - ) - // a code type without a code is invalid - const onlyTypeRow = { ...basicRow, "code | 1": "" } - const onlyTypeResult = validateRow(onlyTypeRow, 24, columns, false) - t.is(onlyTypeResult.length, 1) - t.is( - onlyTypeResult[0].message, - 'A value is required for "code | 1". You must encode the missing information.' - ) - // a code type must be one of BILLING_CODE_TYPES - const wrongTypeRow = { ...basicRow, "code | 1 | type": "GUS" } - const wrongTypeResult = validateRow(wrongTypeRow, 25, columns, false) - t.is(wrongTypeResult.length, 1) - t.is( - wrongTypeResult[0].message, - '"code | 1 | type" value "GUS" is not one of the allowed valid values. You must encode one of these valid values: CPT, HCPCS, ICD, DRG, MS-DRG, R-DRG, S-DRG, APS-DRG, AP-DRG, APR-DRG, APC, NDC, HIPPS, LOCAL, EAPG, CDT, RC, CDM, TRIS-DRG' - ) -}) - -test("validateRow tall conditionals", (t) => { - const columns = [ - ...BASE_COLUMNS, - ...TALL_COLUMNS, - "code | 1", - "code | 1 | type", - "code | 2", - "code | 2 | type", - ] - const basicRow = { - description: "basic description", - setting: "inpatient", - "code | 1": "12345", - "code | 1 | type": "DRG", - "code | 2": "", - "code | 2 | type": "", - drug_unit_of_measurement: "", - drug_type_of_measurement: "", - modifiers: "", - "standard_charge | gross": "100", - "standard_charge | discounted_cash": "200.50", - "standard_charge | min": "50", - "standard_charge | max": "500", - additional_generic_notes: "", - payer_name: "Acme Payer", - plan_name: "Acme Basic Coverage", - "standard_charge | negotiated_dollar": "", - "standard_charge | negotiated_percentage": "", - "standard_charge | negotiated_algorithm": "", - "standard_charge | methodology": "fee schedule", - estimated_amount: "", - } - // If a "payer specific negotiated charge" is encoded as a dollar amount, percentage, or algorithm - // then a corresponding valid value for the payer name, plan name, and standard charge methodology must also be encoded. - const dollarWithInfoRow = { - ...basicRow, - "standard_charge | negotiated_dollar": "500", - } - const dollarWithInfoErrors = validateRow(dollarWithInfoRow, 5, columns, false) - t.is(dollarWithInfoErrors.length, 0) - const dollarMissingInfoRow = { - ...basicRow, - "standard_charge | negotiated_dollar": "500", - payer_name: "", - plan_name: "", - "standard_charge | methodology": "", - } - const dollarMissingInfoErrors = validateRow( - dollarMissingInfoRow, - 6, - columns, - false - ) - t.is(dollarMissingInfoErrors.length, 3) - t.assert( - dollarMissingInfoErrors[0].message.includes( - 'A value is required for "payer_name" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - t.assert( - dollarMissingInfoErrors[1].message.includes( - 'A value is required for "plan_name" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - t.assert( - dollarMissingInfoErrors[2].message.includes( - 'A value is required for "standard_charge | methodology" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - const percentageWithInfoRow = { - ...basicRow, - "standard_charge | negotiated_percentage": "85", - estimated_amount: "60", - } - const percentageWithInfoErrors = validateRow( - percentageWithInfoRow, - 7, - columns, - false - ) - t.is(percentageWithInfoErrors.length, 0) - const percentageMissingInfoRow = { - ...basicRow, - "standard_charge | negotiated_percentage": "85", - estimated_amount: "60", - payer_name: "", - plan_name: "", - "standard_charge | methodology": "", - } - const percentageMissingInfoErrors = validateRow( - percentageMissingInfoRow, - 8, - columns, - false - ) - t.is(percentageMissingInfoErrors.length, 3) - t.assert( - percentageMissingInfoErrors[0].message.includes( - 'A value is required for "payer_name" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - t.assert( - percentageMissingInfoErrors[1].message.includes( - 'A value is required for "plan_name" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - t.assert( - percentageMissingInfoErrors[2].message.includes( - 'A value is required for "standard_charge | methodology" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - - const algorithmWithInfoRow = { - ...basicRow, - "standard_charge | negotiated_algorithm": "85", - estimated_amount: "60", - } - const algorithmWithInfoErrors = validateRow( - algorithmWithInfoRow, - 9, - columns, - false - ) - t.is(algorithmWithInfoErrors.length, 0) - const algorithmMissingInfoRow = { - ...basicRow, - "standard_charge | negotiated_algorithm": "standard method function", - estimated_amount: "60", - payer_name: "", - plan_name: "", - "standard_charge | methodology": "", - } - const algorithmMissingInfoErrors = validateRow( - algorithmMissingInfoRow, - 10, - columns, - false - ) - t.is(algorithmMissingInfoErrors.length, 3) - t.assert( - algorithmMissingInfoErrors[0].message.includes( - 'A value is required for "payer_name" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - t.assert( - algorithmMissingInfoErrors[1].message.includes( - 'A value is required for "plan_name" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - t.assert( - algorithmMissingInfoErrors[2].message.includes( - 'A value is required for "standard_charge | methodology" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - - // If the "standard charge methodology" encoded value is "other", there must be a corresponding explanation found - // in the "additional notes" for the associated payer-specific negotiated charge. - const otherWithNotesRow = { - ...basicRow, - "standard_charge | negotiated_percentage": "80", - estimated_amount: "150", - "standard_charge | methodology": "other", - additional_generic_notes: "explanation of methodology", - } - const otherWithNotesErrors = validateRow( - otherWithNotesRow, - 11, - columns, - false - ) - t.is(otherWithNotesErrors.length, 0) - const otherWithoutNotesRow = { - ...basicRow, - "standard_charge | negotiated_percentage": "80", - estimated_amount: "150", - "standard_charge | methodology": "other", - } - const otherWithoutNotesErrors = validateRow( - otherWithoutNotesRow, - 12, - columns, - false - ) - t.is(otherWithoutNotesErrors.length, 1) - t.is( - otherWithoutNotesErrors[0].message, - 'If the "standard charge methodology" encoded value is "other", there must be a corresponding explanation found in the "additional notes" for the associated payer-specific negotiated charge.' - ) - - // If an item or service is encoded, a corresponding valid value must be encoded for at least one of the following: - // "Gross Charge", "Discounted Cash Price", "Payer-Specific Negotiated Charge: Dollar Amount", "Payer-Specific Negotiated Charge: Percentage", - // "Payer-Specific Negotiated Charge: Algorithm". - const itemNoChargeRow = { - ...basicRow, - "standard_charge | gross": "", - "standard_charge | discounted_cash": "", - } - const itemNoChargeErrors = validateRow(itemNoChargeRow, 28, columns, false) - t.is(itemNoChargeErrors.length, 1) - t.is( - itemNoChargeErrors[0].message, - 'If an item or service is encoded, a corresponding valid value must be encoded for at least one of the following: "Gross Charge", "Discounted Cash Price", "Payer-Specific Negotiated Charge: Dollar Amount", "Payer-Specific Negotiated Charge: Percentage", "Payer-Specific Negotiated Charge: Algorithm".' - ) - const itemGrossChargeRow = { - ...basicRow, - "standard_charge | discounted_cash": "", - } - const itemGrossChargeErrors = validateRow( - itemGrossChargeRow, - 29, - columns, - false - ) - t.is(itemGrossChargeErrors.length, 0) - const itemDiscountedChargeRow = { - ...basicRow, - "standard_charge | gross": "", - } - const itemDiscountedChargeErrors = validateRow( - itemDiscountedChargeRow, - 30, - columns, - false - ) - t.is(itemDiscountedChargeErrors.length, 0) - const itemNegotiatedDollarRow = { - ...basicRow, - "standard_charge | gross": "", - "standard_charge | discounted_cash": "", - "standard_charge | negotiated_dollar": "83", - } - const itemNegotiatedDollarErrors = validateRow( - itemNegotiatedDollarRow, - 31, - columns, - false - ) - t.is(itemNegotiatedDollarErrors.length, 0) - const itemNegotiatedPercentageRow = { - ...basicRow, - "standard_charge | gross": "", - "standard_charge | discounted_cash": "", - "standard_charge | negotiated_percentage": "24", - estimated_amount: "25", - } - const itemNegotiatedPercentageErrors = validateRow( - itemNegotiatedPercentageRow, - 32, - columns, - false - ) - t.is(itemNegotiatedPercentageErrors.length, 0) - const itemNegotiatedAlgorithmRow = { - ...basicRow, - "standard_charge | gross": "", - "standard_charge | discounted_cash": "", - "standard_charge | negotiated_algorithm": "check appendix B", - estimated_amount: "25", - } - const itemNegotiatedAlgorithmErrors = validateRow( - itemNegotiatedAlgorithmRow, - 33, - columns, - false - ) - t.is(itemNegotiatedAlgorithmErrors.length, 0) - - // If there is a "payer specific negotiated charge" encoded as a dollar amount, - // there must be a corresponding valid value encoded for the deidentified minimum and deidentified maximum negotiated charge data. - const dollarNoBoundsRow = { - ...basicRow, - "standard_charge | negotiated_dollar": "300", - "standard_charge | min": "", - "standard_charge | max": "", - } - const dollarNoBoundsErrors = validateRow(dollarNoBoundsRow, 5, columns, false) - t.is(dollarNoBoundsErrors.length, 1) - t.is( - dollarNoBoundsErrors[0].message, - 'If there is a "payer specific negotiated charge" encoded as a dollar amount, there must be a corresponding valid value encoded for the deidentified minimum and deidentified maximum negotiated charge data.' - ) - const percentageNoBoundsRow = { - ...basicRow, - "standard_charge | negotiated_percentage": "80", - "standard_charge | min": "", - "standard_charge | max": "", - estimated_amount: "160", - } - const percentageNoBoundsErrors = validateRow( - percentageNoBoundsRow, - 6, - columns, - false - ) - t.is(percentageNoBoundsErrors.length, 0) - const algorithmNoBoundsRow = { - ...basicRow, - "standard_charge | negotiated_algorithm": "standard logarithm table", - "standard_charge | min": "", - "standard_charge | max": "", - estimated_amount: "160", - } - const algorithmNoBoundsErrors = validateRow( - algorithmNoBoundsRow, - 7, - columns, - false - ) - t.is(algorithmNoBoundsErrors.length, 0) - - // If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, - // then a corresponding "Estimated Allowed Amount" must also be encoded. Required beginning 1/1/2025. - const enforceConditionals = new Date().getFullYear() >= 2025 - const percentageWithEstimateRow = { - ...basicRow, - "standard_charge | negotiated_percentage": "80", - estimated_amount: "150", - } - const percentageWithEstimateErrors = validateRow( - percentageWithEstimateRow, - 8, - columns, - false - ) - t.is(percentageWithEstimateErrors.length, 0) - const percentageNoEstimateRow = { - ...basicRow, - "standard_charge | negotiated_percentage": "80", - estimated_amount: "", - } - const percentageNoEstimateErrors = validateRow( - percentageNoEstimateRow, - 9, - columns, - false - ) - t.is(percentageNoEstimateErrors.length, 1) - t.is( - percentageNoEstimateErrors[0].message, - 'If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, then a corresponding "Estimated Allowed Amount" must also be encoded.' - ) - t.is(percentageNoEstimateErrors[0].warning, !enforceConditionals) - const algorithmWithEstimateRow = { - ...basicRow, - "standard_charge | negotiated_algorithm": "standard logarithm table", - estimated_amount: "150", - } - const algorithmWithEstimateErrors = validateRow( - algorithmWithEstimateRow, - 10, - columns, - false - ) - t.is(algorithmWithEstimateErrors.length, 0) - const algorithmNoEstimateRow = { - ...basicRow, - "standard_charge | negotiated_algorithm": "standard logarithm table", - estimated_amount: "", - } - const algorithmNoEstimateErrors = validateRow( - algorithmNoEstimateRow, - 11, - columns, - false - ) - t.is(algorithmNoEstimateErrors.length, 1) - t.is( - algorithmNoEstimateErrors[0].message, - 'If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, then a corresponding "Estimated Allowed Amount" must also be encoded.' - ) - t.is(algorithmNoEstimateErrors[0].warning, !enforceConditionals) - - // If code type is NDC, then the corresponding drug unit of measure and - // drug type of measure data elements must be encoded. Required beginning 1/1/2025. - const ndcNoMeasurementRow = { - ...basicRow, - "code | 1 | type": "NDC", - "standard_charge | negotiated_dollar": "300", - drug_unit_of_measurement: "", - drug_type_of_measurement: "", - } - const ndcNoMeasurementErrors = validateRow( - ndcNoMeasurementRow, - 12, - columns, - false - ) - t.is(ndcNoMeasurementErrors.length, 1) - t.is( - ndcNoMeasurementErrors[0].message, - "If code type is NDC, then the corresponding drug unit of measure and drug type of measure data element must be encoded." - ) - t.is(ndcNoMeasurementErrors[0].warning, !enforceConditionals) - const ndcSecondNoMeasurementRow = { - ...basicRow, - "code | 2": "12345", - "code | 2 | type": "NDC", - "standard_charge | negotiated_dollar": "300", - drug_unit_of_measurement: "", - drug_type_of_measurement: "", - } - const ndcSecondNoMeasurementErrors = validateRow( - ndcSecondNoMeasurementRow, - 13, - columns, - false - ) - t.is(ndcSecondNoMeasurementErrors.length, 1) - t.is( - ndcSecondNoMeasurementErrors[0].message, - "If code type is NDC, then the corresponding drug unit of measure and drug type of measure data element must be encoded." - ) - t.is(ndcSecondNoMeasurementErrors[0].warning, !enforceConditionals) - // If a modifier is encoded without an item or service, then a description and one of the following - // is the minimum information required: - // additional_generic_notes, standard_charge | negotiated_dollar, standard_charge | negotiated_percentage, or standard_charge | negotiated_algorithm - const invalidModifierRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - } - const invalidModifierErrors = validateRow( - invalidModifierRow, - 14, - columns, - false - ) - t.is(invalidModifierErrors.length, 1) - t.is( - invalidModifierErrors[0].message, - "If a modifier is encoded without an item or service, then a description and one of the following is the minimum information required: additional_payer_notes, standard_charge | negotiated_dollar, standard_charge | negotiated_percentage, or standard_charge | negotiated_algorithm." - ) - const modifierWithNotesRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - additional_generic_notes: "useful notes about the modifier", - } - const modifierWithNotesErrors = validateRow( - modifierWithNotesRow, - 15, - columns, - false - ) - t.is(modifierWithNotesErrors.length, 0) - const modifierWithDollarRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - "standard_charge | negotiated_dollar": "380", - } - const modifierWithDollarErrors = validateRow( - modifierWithDollarRow, - 16, - columns, - false - ) - t.is(modifierWithDollarErrors.length, 0) - const modifierWithPercentageRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - "standard_charge | negotiated_percentage": "48.5", - estimated_amount: "150", - } - const modifierWithPercentageErrors = validateRow( - modifierWithPercentageRow, - 17, - columns, - false - ) - t.is(modifierWithPercentageErrors.length, 0) - const modifierWithAlgorithmRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - "standard_charge | negotiated_algorithm": "sliding function", - estimated_amount: "150", - } - const modifierWithAlgorithmErrors = validateRow( - modifierWithAlgorithmRow, - 18, - columns, - false - ) - t.is(modifierWithAlgorithmErrors.length, 0) - // types are still enforced for a modifier row - const modifierWithWrongTypesRow = { - ...basicRow, - "standard_charge | negotiated_dollar": "$100", - "standard_charge | negotiated_percentage": "15%", - "standard_charge | methodology": "secret", - } - const modifierWithWrongTypesErrors = validateRow( - modifierWithWrongTypesRow, - 19, - columns, - false - ) - t.is(modifierWithWrongTypesErrors.length, 3) - t.is( - modifierWithWrongTypesErrors[0].message, - '"standard_charge | negotiated_dollar" value "$100" is not a positive number. You must encode a positive, non-zero, numeric value.' - ) - t.is( - modifierWithWrongTypesErrors[1].message, - '"standard_charge | negotiated_percentage" value "15%" is not a positive number. You must encode a positive, non-zero, numeric value.' - ) - t.is( - modifierWithWrongTypesErrors[2].message, - '"standard_charge | methodology" value "secret" is not one of the allowed valid values. You must encode one of these valid values: case rate, fee schedule, percent of total billed charges, per diem, other' - ) -}) - -test("validateColumns wide", (t) => { - const columns = [ - ...BASE_COLUMNS, - "code | 1", - "code | 1 | type", - "standard_charge | Payer One | Basic Plan | negotiated_dollar", - "standard_charge | Payer One | Basic Plan | negotiated_percentage", - "standard_charge | Payer One | Basic Plan | negotiated_algorithm", - "standard_charge | Payer One | Basic Plan | methodology", - "estimated_amount | Payer One | Basic Plan", - "additional_payer_notes | Payer One | Basic Plan", - ] - - //The idea is that a column could be misnamed. - const additionalColumns = [ - "standard_charge|[payer_AETNA LIFE AND CAUSAULTY | HMO/PPO]", - "standard_charge|[payer_AETNA LIFE AND CAUSAULTY | HMO/PPO] |percent", - "standard_charge|[payer_AETNA LIFE AND CAUSAULTY | HMO/PPO] |contracting_method", - "additional_payer_notes |[payer_AETNA LIFE AND CAUSAULTY | HMO/PPO]", - "standard_charge|[payer_ASR HEALTH BEN CIGNA | COMMERCIAL]", - "standard_charge|[payer_ASR HEALTH BEN CIGNA | COMMERCIAL]", - ] - t.is(validateColumns(columns).length, 0) - // any order is okay - const reverseColumns = [...columns].reverse() - t.is(validateColumns(reverseColumns).length, 0) - // the full group of columns for a payer and plan must appear - // estimated amount is only required in 2025 - const enforce2025 = new Date().getFullYear() >= 2025 - const someColumnsMissing = columns.slice(0, -2) - const someColumnsMissingErrors = validateColumns(someColumnsMissing) - t.is(someColumnsMissingErrors.length, 2) - t.is( - someColumnsMissingErrors[0].message, - "Column estimated_amount | Payer One | Basic Plan is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary." - ) - t.is(someColumnsMissingErrors[0].warning, enforce2025 ? undefined : true) - t.is( - someColumnsMissingErrors[1].message, - "Column additional_payer_notes | Payer One | Basic Plan is miscoded or missing from row 3. You must include this column and confirm that it is encoded as specified in the data dictionary." - ) - t.is(someColumnsMissingErrors[1].warning, undefined) - - const customWideColumns = [...columns, ...additionalColumns] - - const someDuplicateErrors = validateColumns(customWideColumns) - t.is(someDuplicateErrors.length, 12) -}) - -test("validateRow wide conditionals", (t) => { - const columns = [ - ...BASE_COLUMNS, - "code | 1", - "code | 1 | type", - "code | 2", - "code | 2 | type", - "standard_charge | Payer One | Basic Plan | negotiated_dollar", - "standard_charge | Payer One | Basic Plan | negotiated_percentage", - "standard_charge | Payer One | Basic Plan | negotiated_algorithm", - "estimated_amount | Payer One | Basic Plan", - "standard_charge | Payer One | Basic Plan | methodology", - "additional_payer_notes | Payer One | Basic Plan", - "standard_charge | Payer Two | Special Plan | negotiated_dollar", - "standard_charge | Payer Two | Special Plan | negotiated_percentage", - "standard_charge | Payer Two | Special Plan | negotiated_algorithm", - "estimated_amount | Payer Two | Special Plan", - "standard_charge | Payer Two | Special Plan | methodology", - "additional_payer_notes | Payer Two | Special Plan", - ] - const basicRow = { - description: "basic description", - setting: "inpatient", - "code | 1": "12345", - "code | 1 | type": "DRG", - "code | 2": "", - "code | 2 | type": "", - drug_unit_of_measurement: "", - drug_type_of_measurement: "", - modifiers: "", - "standard_charge | gross": "100", - "standard_charge | discounted_cash": "200.50", - "standard_charge | min": "50", - "standard_charge | max": "500", - additional_generic_notes: "", - "standard_charge | Payer One | Basic Plan | negotiated_dollar": "", - "standard_charge | Payer One | Basic Plan | negotiated_percentage": "", - "standard_charge | Payer One | Basic Plan | negotiated_algorithm": "", - "estimated_amount | Payer One | Basic Plan": "", - "standard_charge | Payer One | Basic Plan | methodology": "", - "additional_payer_notes | Payer One | Basic Plan": "", - "standard_charge | Payer Two | Special Plan | negotiated_dollar": "", - "standard_charge | Payer Two | Special Plan | negotiated_percentage": "", - "standard_charge | Payer Two | Special Plan | negotiated_algorithm": "", - "estimated_amount | Payer Two | Special Plan": "", - "standard_charge | Payer Two | Special Plan | methodology": "", - "additional_payer_notes | Payer Two | Special Plan": "", - } - - // If a "payer specific negotiated charge" is encoded as a dollar amount, percentage, or algorithm - // then a corresponding valid value for the payer name, plan name, and standard charge methodology must also be encoded. - // Since the wide format incorporates payer name and plan name into the column name, only methodology is checked. - const dollarWithMethodologyRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_dollar": "552", - "standard_charge | Payer One | Basic Plan | methodology": "fee schedule", - } - const dollarWithMethodologyErrors = validateRow( - dollarWithMethodologyRow, - 5, - columns, - true - ) - t.is(dollarWithMethodologyErrors.length, 0) - const dollarNoMethodologyRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_dollar": "552", - } - const dollarNoMethodologyErrors = validateRow( - dollarNoMethodologyRow, - 6, - columns, - true - ) - t.is(dollarNoMethodologyErrors.length, 1) - t.assert( - dollarNoMethodologyErrors[0].message.includes( - 'A value is required for "standard_charge | Payer One | Basic Plan | methodology" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - const dollarWrongMethodologyRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_dollar": "552", - "standard_charge | Payer Two | Special Plan | methodology": "fee schedule", - } - const dollarWrongMethodologyErrors = validateRow( - dollarWrongMethodologyRow, - 7, - columns, - true - ) - t.is(dollarWrongMethodologyErrors.length, 1) - t.assert( - dollarWrongMethodologyErrors[0].message.includes( - 'A value is required for "standard_charge | Payer One | Basic Plan | methodology" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - const percentageWithMethodologyRow = { - ...basicRow, - "standard_charge | Payer Two | Special Plan | negotiated_percentage": "50", - "estimated_amount | Payer Two | Special Plan": "244", - "standard_charge | Payer Two | Special Plan | methodology": "fee schedule", - } - const percentageWithMethodologyErrors = validateRow( - percentageWithMethodologyRow, - 8, - columns, - true - ) - t.is(percentageWithMethodologyErrors.length, 0) - const percentageNoMethodologyRow = { - ...basicRow, - "standard_charge | Payer Two | Special Plan | negotiated_percentage": "50", - "estimated_amount | Payer Two | Special Plan": "244", - } - const percentageNoMethodologyErrors = validateRow( - percentageNoMethodologyRow, - 9, - columns, - true - ) - t.is(percentageNoMethodologyErrors.length, 1) - t.assert( - percentageNoMethodologyErrors[0].message.includes( - 'A value is required for "standard_charge | Payer Two | Special Plan | methodology" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - const percentageWrongMethodologyRow = { - ...basicRow, - "standard_charge | Payer Two | Special Plan | negotiated_percentage": "50", - "estimated_amount | Payer Two | Special Plan": "244", - "standard_charge | Payer One | Basic Plan | methodology": "fee schedule", - } - const percentageWrongMethodologyErrors = validateRow( - percentageWrongMethodologyRow, - 10, - columns, - true - ) - t.is(percentageWrongMethodologyErrors.length, 1) - t.assert( - percentageWrongMethodologyErrors[0].message.includes( - 'A value is required for "standard_charge | Payer Two | Special Plan | methodology" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - const algorithmWithMethodologyRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_algorithm": - "consult the appendix", - "estimated_amount | Payer One | Basic Plan": "245", - "standard_charge | Payer One | Basic Plan | methodology": "fee schedule", - } - const algorithmWithMethodologyErrors = validateRow( - algorithmWithMethodologyRow, - 11, - columns, - true - ) - t.is(algorithmWithMethodologyErrors.length, 0) - const algorithmNoMethodologyRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_algorithm": - "consult the appendix", - "estimated_amount | Payer One | Basic Plan": "245", - } - const algorithmNoMethodologyErrors = validateRow( - algorithmNoMethodologyRow, - 12, - columns, - true - ) - t.is(algorithmNoMethodologyErrors.length, 1) - t.assert( - algorithmNoMethodologyErrors[0].message.includes( - 'A value is required for "standard_charge | Payer One | Basic Plan | methodology" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - const algorithmWrongMethodologyRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_algorithm": - "consult the appendix", - "estimated_amount | Payer One | Basic Plan": "245", - "standard_charge | Payer Two | Special Plan | methodology": "fee schedule", - } - const algorithmWrongMethodologyErrors = validateRow( - algorithmWrongMethodologyRow, - 12, - columns, - true - ) - t.is(algorithmWrongMethodologyErrors.length, 1) - t.assert( - algorithmWrongMethodologyErrors[0].message.includes( - 'A value is required for "standard_charge | Payer One | Basic Plan | methodology" when a payer specific negotiated charge is encoded as a dollar amount, percentage, or algorithm' - ) - ) - const algorithmInvalidMethodologyRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_algorithm": - "consult the appendix", - "estimated_amount | Payer One | Basic Plan": "245", - "standard_charge | Payer One | Basic Plan | methodology": - "special methodology", - } - const algorithmInvalidMethodologyErrors = validateRow( - algorithmInvalidMethodologyRow, - 13, - columns, - true - ) - t.is(algorithmInvalidMethodologyErrors.length, 1) - t.assert( - algorithmInvalidMethodologyErrors[0].message.includes( - '"standard_charge | Payer One | Basic Plan | methodology" value "special methodology" is not one of the allowed valid values' - ) - ) - - // If the "standard charge methodology" encoded value is "other", there must be a corresponding explanation found - // in the "additional notes" for the associated payer-specific negotiated charge. - const otherWithNotesRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_percentage": "80", - "estimated_amount | Payer One | Basic Plan": "150", - "standard_charge | Payer One | Basic Plan | methodology": "other", - "additional_payer_notes | Payer One | Basic Plan": - "explanation of methodology", - } - const otherWithNotesErrors = validateRow(otherWithNotesRow, 51, columns, true) - t.is(otherWithNotesErrors.length, 0) - const otherWithoutNotesRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_percentage": "80", - "estimated_amount | Payer One | Basic Plan": "150", - "standard_charge | Payer One | Basic Plan | methodology": "other", - "additional_payer_notes | Payer One | Basic Plan": "", - } - const otherWithoutNotesErrors = validateRow( - otherWithoutNotesRow, - 52, - columns, - true - ) - t.is(otherWithoutNotesErrors.length, 1) - t.is( - otherWithoutNotesErrors[0].message, - 'If the "standard charge methodology" encoded value is "other", there must be a corresponding explanation found in the "additional notes" for the associated payer-specific negotiated charge.' - ) - const otherWrongNotesRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_percentage": "80", - "estimated_amount | Payer One | Basic Plan": "150", - "standard_charge | Payer One | Basic Plan | methodology": "other", - "additional_payer_notes | Payer Two | Special Plan": "important notes here", - } - const otherWrongNotesErrors = validateRow( - otherWrongNotesRow, - 53, - columns, - true - ) - t.is(otherWrongNotesErrors.length, 1) - t.is( - otherWrongNotesErrors[0].message, - 'If the "standard charge methodology" encoded value is "other", there must be a corresponding explanation found in the "additional notes" for the associated payer-specific negotiated charge.' - ) - - // If an item or service is encoded, a corresponding valid value must be encoded for at least one of the following: - // "Gross Charge", "Discounted Cash Price", "Payer-Specific Negotiated Charge: Dollar Amount", "Payer-Specific Negotiated Charge: Percentage", - // "Payer-Specific Negotiated Charge: Algorithm". - const itemNoChargeRow = { - ...basicRow, - "standard_charge | gross": "", - "standard_charge | discounted_cash": "", - } - const itemNoChargeErrors = validateRow(itemNoChargeRow, 54, columns, true) - t.is(itemNoChargeErrors.length, 1) - t.is( - itemNoChargeErrors[0].message, - 'If an item or service is encoded, a corresponding valid value must be encoded for at least one of the following: "Gross Charge", "Discounted Cash Price", "Payer-Specific Negotiated Charge: Dollar Amount", "Payer-Specific Negotiated Charge: Percentage", "Payer-Specific Negotiated Charge: Algorithm".' - ) - - const itemGrossChargeRow = { - ...basicRow, - "standard_charge | discounted_cash": "", - } - const itemGrossChargeErrors = validateRow( - itemGrossChargeRow, - 55, - columns, - true - ) - t.is(itemGrossChargeErrors.length, 0) - const itemDiscountedChargeRow = { - ...basicRow, - "standard_charge | gross": "", - } - const itemDiscountedChargeErrors = validateRow( - itemDiscountedChargeRow, - 56, - columns, - true - ) - t.is(itemDiscountedChargeErrors.length, 0) - const itemNegotiatedDollarRow = { - ...basicRow, - "standard_charge | gross": "", - "standard_charge | discounted_cash": "", - "standard_charge | Payer One | Basic Plan | negotiated_dollar": "83", - "standard_charge | Payer One | Basic Plan | methodology": "case rate", - } - const itemNegotiatedDollarErrors = validateRow( - itemNegotiatedDollarRow, - 57, - columns, - true - ) - t.is(itemNegotiatedDollarErrors.length, 0) - const itemNegotiatedPercentageRow = { - ...basicRow, - "standard_charge | gross": "", - "standard_charge | discounted_cash": "", - "standard_charge | Payer Two | Special Plan | negotiated_percentage": "24", - "standard_charge | Payer Two | Special Plan | methodology": "case rate", - "estimated_amount | Payer Two | Special Plan": "25", - } - const itemNegotiatedPercentageErrors = validateRow( - itemNegotiatedPercentageRow, - 58, - columns, - true - ) - t.is(itemNegotiatedPercentageErrors.length, 0) - const itemNegotiatedAlgorithmRow = { - ...basicRow, - "standard_charge | gross": "", - "standard_charge | discounted_cash": "", - "standard_charge | Payer One | Basic Plan | negotiated_algorithm": - "check appendix B", - "standard_charge | Payer One | Basic Plan | methodology": "case rate", - "estimated_amount | Payer One | Basic Plan": "25", - } - const itemNegotiatedAlgorithmErrors = validateRow( - itemNegotiatedAlgorithmRow, - 59, - columns, - true - ) - t.is(itemNegotiatedAlgorithmErrors.length, 0) - - // If there is a "payer specific negotiated charge" encoded as a dollar amount, - // there must be a corresponding valid value encoded for the deidentified minimum and deidentified maximum negotiated charge data. - const dollarNoBoundsRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_dollar": "300", - "standard_charge | Payer One | Basic Plan | methodology": "case rate", - "standard_charge | min": "", - "standard_charge | max": "", - } - const dollarNoBoundsErrors = validateRow(dollarNoBoundsRow, 5, columns, true) - t.is(dollarNoBoundsErrors.length, 1) - t.is( - dollarNoBoundsErrors[0].message, - 'If there is a "payer specific negotiated charge" encoded as a dollar amount, there must be a corresponding valid value encoded for the deidentified minimum and deidentified maximum negotiated charge data.' - ) - const percentageNoBoundsRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_percentage": "80", - "standard_charge | Payer One | Basic Plan | methodology": "case rate", - "standard_charge | min": "", - "standard_charge | max": "", - "estimated_amount | Payer One | Basic Plan": "160", - } - const percentageNoBoundsErrors = validateRow( - percentageNoBoundsRow, - 6, - columns, - true - ) - t.is(percentageNoBoundsErrors.length, 0) - const algorithmNoBoundsRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_algorithm": - "standard logarithm table", - "standard_charge | Payer One | Basic Plan | methodology": "case rate", - "standard_charge | min": "", - "standard_charge | max": "", - "estimated_amount | Payer One | Basic Plan": "160", - } - const algorithmNoBoundsErrors = validateRow( - algorithmNoBoundsRow, - 7, - columns, - true - ) - t.is(algorithmNoBoundsErrors.length, 0) - - // If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, - // then a corresponding "Estimated Allowed Amount" must also be encoded. Required beginning 1/1/2025. - const enforceConditionals = new Date().getFullYear() >= 2025 - - const percentageWithEstimateRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_percentage": "80", - "standard_charge | Payer One | Basic Plan | methodology": "case rate", - "estimated_amount | Payer One | Basic Plan": "160", - } - const percentageWithEstimateErrors = validateRow( - percentageWithEstimateRow, - 8, - columns, - true - ) - t.is(percentageWithEstimateErrors.length, 0) - const percentageNoEstimateRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_percentage": "80", - "standard_charge | Payer One | Basic Plan | methodology": "case rate", - } - const percentageNoEstimateErrors = validateRow( - percentageNoEstimateRow, - 9, - columns, - true - ) - t.is(percentageNoEstimateErrors.length, 1) - t.is( - percentageNoEstimateErrors[0].message, - 'If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, then a corresponding "Estimated Allowed Amount" must also be encoded.' - ) - t.is(percentageNoEstimateErrors[0].warning, !enforceConditionals) - const percentageWrongEstimateRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_percentage": "80", - "standard_charge | Payer One | Basic Plan | methodology": "case rate", - "estimated_amount | Payer Two | Special Plan": "55", - } - const percentageWrongEstimateErrors = validateRow( - percentageWrongEstimateRow, - 10, - columns, - true - ) - t.is(percentageWrongEstimateErrors.length, 1) - t.is( - percentageWrongEstimateErrors[0].message, - 'If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, then a corresponding "Estimated Allowed Amount" must also be encoded.' - ) - t.is(percentageWrongEstimateErrors[0].warning, !enforceConditionals) - const algorithmWithEstimateRow = { - ...basicRow, - "standard_charge | Payer Two | Special Plan | negotiated_algorithm": - "useful function", - "standard_charge | Payer Two | Special Plan | methodology": "case rate", - "estimated_amount | Payer Two | Special Plan": "55", - } - const algorithmWithEstimateErrors = validateRow( - algorithmWithEstimateRow, - 11, - columns, - true - ) - t.is(algorithmWithEstimateErrors.length, 0) - const algorithmNoEstimateRow = { - ...basicRow, - "standard_charge | Payer Two | Special Plan | negotiated_algorithm": - "useful function", - "standard_charge | Payer Two | Special Plan | methodology": "case rate", - } - const algorithmNoEstimateErrors = validateRow( - algorithmNoEstimateRow, - 12, - columns, - true - ) - t.is(algorithmNoEstimateErrors.length, 1) - t.is( - algorithmNoEstimateErrors[0].message, - 'If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, then a corresponding "Estimated Allowed Amount" must also be encoded.' - ) - t.is(algorithmNoEstimateErrors[0].warning, !enforceConditionals) - const algorithmWrongEstimateRow = { - ...basicRow, - "standard_charge | Payer Two | Special Plan | negotiated_algorithm": - "useful function", - "standard_charge | Payer Two | Special Plan | methodology": "case rate", - "estimated_amount | Payer One | Basic Plan": "55", - } - const algorithmWrongEstimateErrors = validateRow( - algorithmWrongEstimateRow, - 13, - columns, - true - ) - t.is(algorithmWrongEstimateErrors.length, 1) - t.is( - algorithmWrongEstimateErrors[0].message, - 'If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, then a corresponding "Estimated Allowed Amount" must also be encoded.' - ) - t.is(algorithmWrongEstimateErrors[0].warning, !enforceConditionals) - - // If code type is NDC, then the corresponding drug unit of measure and - // drug type of measure data elements must be encoded. Required beginning 1/1/2025. - const ndcNoMeasurementRow = { - ...basicRow, - "code | 1 | type": "NDC", - "standard_charge | Payer One | Basic Plan | negotiated_dollar": "300", - "standard_charge | Payer One | Basic Plan | methodology": "case rate", - drug_unit_of_measurement: "", - drug_type_of_measurement: "", - } - const ndcNoMeasurementErrors = validateRow( - ndcNoMeasurementRow, - 14, - columns, - true - ) - t.is(ndcNoMeasurementErrors.length, 1) - t.is( - ndcNoMeasurementErrors[0].message, - "If code type is NDC, then the corresponding drug unit of measure and drug type of measure data element must be encoded." - ) - t.is(ndcNoMeasurementErrors[0].warning, !enforceConditionals) - const ndcSecondNoMeasurementRow = { - ...basicRow, - "code | 2": "12345", - "code | 2 | type": "NDC", - "standard_charge | Payer One | Basic Plan | negotiated_dollar": "300", - "standard_charge | Payer One | Basic Plan | methodology": "case rate", - drug_unit_of_measurement: "", - drug_type_of_measurement: "", - } - const ndcSecondNoMeasurementErrors = validateRow( - ndcSecondNoMeasurementRow, - 15, - columns, - true - ) - t.is(ndcSecondNoMeasurementErrors.length, 1) - t.is( - ndcSecondNoMeasurementErrors[0].message, - "If code type is NDC, then the corresponding drug unit of measure and drug type of measure data element must be encoded." - ) - t.is(ndcSecondNoMeasurementErrors[0].warning, !enforceConditionals) - - // If a modifier is encoded without an item or service, then a description and one of the following - // is the minimum information required: - // additional_generic_notes, additional_payer_notes, standard_charge | negotiated_dollar, - // standard_charge | negotiated_percentage, or standard_charge | negotiated_algorithm - const invalidModifierRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - } - const invalidModifierErrors = validateRow( - invalidModifierRow, - 16, - columns, - true - ) - t.is(invalidModifierErrors.length, 1) - t.is( - invalidModifierErrors[0].message, - "If a modifier is encoded without an item or service, then a description and one of the following is the minimum information required: additional_payer_notes, standard_charge | negotiated_dollar, standard_charge | negotiated_percentage, or standard_charge | negotiated_algorithm." - ) - const modifierWithGenericNotesRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - additional_generic_notes: "useful notes about the modifier", - } - const modifierWithGenericNotesErrors = validateRow( - modifierWithGenericNotesRow, - 17, - columns, - true - ) - t.is(modifierWithGenericNotesErrors.length, 0) - const modifierWithPayerNotesRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - "additional_payer_notes | Payer One | Basic Plan": - "useful notes for this payer", - } - const modifierWithPayerNotesErrors = validateRow( - modifierWithPayerNotesRow, - 18, - columns, - true - ) - t.is(modifierWithPayerNotesErrors.length, 0) - const modifierWithDollarRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - "standard_charge | Payer Two | Special Plan | negotiated_dollar": "151", - } - const modifierWithDollarErrors = validateRow( - modifierWithDollarRow, - 19, - columns, - true - ) - t.is(modifierWithDollarErrors.length, 0) - const modifierWithPercentageRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - "standard_charge | Payer One | Basic Plan | negotiated_percentage": "110", - } - const modifierWithPercentageErrors = validateRow( - modifierWithPercentageRow, - 20, - columns, - true - ) - t.is(modifierWithPercentageErrors.length, 0) - const modifierWithAlgorithmRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - "standard_charge | Payer Two | Special Plan | negotiated_algorithm": - "consult the table of numbers", - } - const modifierWithAlgorithmErrors = validateRow( - modifierWithAlgorithmRow, - 21, - columns, - true - ) - t.is(modifierWithAlgorithmErrors.length, 0) - // types are still enforced for a modifier row - const modifierWithWrongTypesRow = { - ...basicRow, - "code | 1": "", - "code | 1 | type": "", - modifiers: "50", - "standard_charge | Payer One | Basic Plan | negotiated_dollar": "$100", - "standard_charge | Payer One | Basic Plan | negotiated_percentage": "15%", - "standard_charge | Payer Two | Special Plan | methodology": "secret", - } - const modifierWithWrongTypesErrors = validateRow( - modifierWithWrongTypesRow, - 22, - columns, - true - ) - t.is(modifierWithWrongTypesErrors.length, 3) - t.is( - modifierWithWrongTypesErrors[0].message, - '"standard_charge | Payer One | Basic Plan | negotiated_dollar" value "$100" is not a positive number. You must encode a positive, non-zero, numeric value.' - ) - t.is( - modifierWithWrongTypesErrors[1].message, - '"standard_charge | Payer One | Basic Plan | negotiated_percentage" value "15%" is not a positive number. You must encode a positive, non-zero, numeric value.' - ) - t.is( - modifierWithWrongTypesErrors[2].message, - '"standard_charge | Payer Two | Special Plan | methodology" value "secret" is not one of the allowed valid values. You must encode one of these valid values: case rate, fee schedule, percent of total billed charges, per diem, other' - ) - - const zeroNumericRow = { - ...basicRow, - "standard_charge | Payer One | Basic Plan | negotiated_dollar": "0", - "standard_charge | Payer One | Basic Plan | negotiated_percentage": "0", - "standard_charge | Payer One | Basic Plan | methodology": "per diem", - } - const zeroNumericRowErrors = validateRow(zeroNumericRow, 22, columns, true) - t.is(zeroNumericRowErrors.length, 2) - t.is( - zeroNumericRowErrors[0].message, - '"standard_charge | Payer One | Basic Plan | negotiated_dollar" value "0" is not a positive number. You must encode a positive, non-zero, numeric value.' - ) - t.is( - zeroNumericRowErrors[1].message, - '"standard_charge | Payer One | Basic Plan | negotiated_percentage" value "0" is not a positive number. You must encode a positive, non-zero, numeric value.' - ) -}) diff --git a/test/2.0/json.spec.ts b/test/2.0/json.spec.ts deleted file mode 100644 index 50c78f2..0000000 --- a/test/2.0/json.spec.ts +++ /dev/null @@ -1,292 +0,0 @@ -import test from "ava" -import { loadFixtureStream } from "../utils.js" -import { validateJson } from "../../src/json.js" - -test("validateJson", async (t) => { - const result = await validateJson( - loadFixtureStream("/2.0/sample-valid.json"), - "v2.0" - ) - t.is(result.valid, true) - t.deepEqual(result.errors.length, 0) -}) - -test("validateJson BOM", async (t) => { - const result = await validateJson( - loadFixtureStream("/2.0/sample-valid-bom.json"), - "v2.0" - ) - t.is(result.valid, true) - t.deepEqual(result.errors.length, 0) -}) - -test("validateJson empty", async (t) => { - const result = await validateJson( - loadFixtureStream("/2.0/sample-empty.json"), - "v2.0" - ) - t.is(result.valid, false) - t.deepEqual(result.errors.length, 8) -}) - -test("validateJson syntactically invalid", async (t) => { - const result = await validateJson( - loadFixtureStream("/2.0/sample-invalid.json"), - "v2.0" - ) - t.is(result.valid, false) - t.deepEqual(result.errors.length, 1) -}) - -test("validateJson maxErrors", async (t) => { - const result = await validateJson( - loadFixtureStream("/2.0/sample-empty.json"), - "v2.0", - { - maxErrors: 1, - } - ) - t.is(result.valid, false) - t.deepEqual(result.errors.length, 1) -}) - -test("validateJson errorFile", async (t) => { - const result = await validateJson( - loadFixtureStream("/2.0/sample-errors.json"), - "v2.0" - ) - t.is(result.valid, false) - t.deepEqual(result.errors.length, 3) - t.deepEqual(result.errors, [ - { - path: "/standard_charge_information/0/standard_charges/0/payers_information/0/standard_charge_dollar", - field: "standard_charge_dollar", - message: "must be number", - }, - { - path: "/standard_charge_information/3/standard_charges/0/payers_information/2", - field: "2", - message: "must have required property 'methodology'", - }, - { - path: "/affirmation/affirmation", - field: "affirmation", - message: "must be equal to constant", - }, - ]) -}) - -test("validateJsonConditionals", async (t) => { - const result = await validateJson( - loadFixtureStream("/2.0/sample-conditional-errors.json"), - "v2.0" - ) - t.is(result.valid, false) - t.deepEqual(result.errors.length, 2) - t.deepEqual(result.errors, [ - { - path: "/standard_charge_information/3/standard_charges/0/payers_information/2", - field: "2", - message: "must have required property 'additional_payer_notes'", - }, - { - path: "/standard_charge_information/3/standard_charges/0/payers_information/2", - field: "2", - message: 'must match "then" schema', - }, - ]) - - const result2 = await validateJson( - loadFixtureStream("/2.0/sample-conditional-error-standardcharge.json"), - "v2.0" - ) - t.is(result2.valid, false) - t.deepEqual(result2.errors.length, 11) - t.deepEqual(result2.errors, [ - { - path: "/standard_charge_information/0/standard_charges/0", - field: "0", - message: "must have required property 'gross_charge'", - }, - { - path: "/standard_charge_information/0/standard_charges/0", - field: "0", - message: "must have required property 'discounted_cash'", - }, - { - path: "/standard_charge_information/0/standard_charges/0", - field: "0", - message: "must have required property 'payers_information'", - }, - { - path: "/standard_charge_information/0/standard_charges/0", - field: "0", - message: "must match a schema in anyOf", - }, - { - path: "/standard_charge_information/1/standard_charges/0", - field: "0", - message: "must have required property 'gross_charge'", - }, - { - path: "/standard_charge_information/1/standard_charges/0", - field: "0", - message: "must have required property 'discounted_cash'", - }, - { - path: "/standard_charge_information/1/standard_charges/0/payers_information/0", - field: "0", - message: "must have required property 'standard_charge_dollar'", - }, - { - path: "/standard_charge_information/1/standard_charges/0/payers_information/0", - field: "0", - message: "must have required property 'standard_charge_algorithm'", - }, - { - path: "/standard_charge_information/1/standard_charges/0/payers_information/0", - field: "0", - message: "must have required property 'standard_charge_percentage'", - }, - { - path: "/standard_charge_information/1/standard_charges/0/payers_information/0", - field: "0", - message: "must match a schema in anyOf", - }, - { - path: "/standard_charge_information/1/standard_charges/0", - field: "0", - message: "must match a schema in anyOf", - }, - ]) - - const result3 = await validateJson( - loadFixtureStream("/2.0/sample-conditional-error-minimum.json"), - "v2.0" - ) - t.is(result3.valid, false) - t.deepEqual(result3.errors.length, 7) - t.deepEqual(result3.errors, [ - { - path: "/standard_charge_information/1/standard_charges/0", - field: "0", - message: "must have required property 'minimum'", - }, - { - path: "/standard_charge_information/1/standard_charges/0", - field: "0", - message: "must have required property 'maximum'", - }, - { - path: "/standard_charge_information/1/standard_charges/0", - field: "0", - message: 'must match "then" schema', - }, - { - path: "/standard_charge_information/2/standard_charges/0", - field: "0", - message: "must have required property 'maximum'", - }, - { - path: "/standard_charge_information/2/standard_charges/0", - field: "0", - message: 'must match "then" schema', - }, - { - path: "/standard_charge_information/3/standard_charges/0", - field: "0", - message: "must have required property 'maximum'", - }, - { - path: "/standard_charge_information/3/standard_charges/0", - field: "0", - message: 'must match "then" schema', - }, - ]) -}) - -test("validateJson estimated amount conditional", async (t) => { - const enforce2025 = new Date().getFullYear() >= 2025 - const result = await validateJson( - loadFixtureStream("/2.0/sample-conditional-error-estimate.json"), - "v2.0" - ) - t.is(result.valid, !enforce2025) - t.is(result.errors.length, 2) - t.deepEqual(result.errors, [ - { - path: "/standard_charge_information/0/standard_charges/0/payers_information/3", - field: "3", - message: "must have required property 'estimated_amount'", - warning: enforce2025 ? undefined : true, - }, - { - path: "/standard_charge_information/0/standard_charges/0/payers_information/3", - field: "3", - message: 'must match "then" schema', - warning: enforce2025 ? undefined : true, - }, - ]) -}) - -test("validateJson NDC drug information conditional", async (t) => { - const enforce2025 = new Date().getFullYear() >= 2025 - const result = await validateJson( - loadFixtureStream("/2.0/sample-conditional-error-ndc.json"), - "v2.0" - ) - t.is(result.valid, !enforce2025) - t.is(result.errors.length, 2) - t.deepEqual(result.errors, [ - { - path: "/standard_charge_information/0", - field: "", - message: "must have required property 'drug_information'", - warning: enforce2025 ? undefined : true, - }, - { - path: "/standard_charge_information/0", - field: "", - message: 'must match "then" schema', - warning: enforce2025 ? undefined : true, - }, - ]) -}) - -test("validateJson 2025 properties", async (t) => { - const enforce2025 = new Date().getFullYear() >= 2025 - const result = await validateJson( - loadFixtureStream("/2.0/sample-2025-properties.json"), - "v2.0" - ) - t.is(result.valid, !enforce2025) - t.is(result.errors.length, 3) - t.deepEqual(result.errors, [ - { - path: "/standard_charge_information/0/drug_information", - field: "drug_information", - message: "must have required property 'type'", - warning: enforce2025 ? undefined : true, - }, - { - path: "/standard_charge_information/1/standard_charges/0/payers_information/1/estimated_amount", - field: "estimated_amount", - message: "must be number", - warning: enforce2025 ? undefined : true, - }, - { - path: "/modifier_information/0", - field: "0", - message: "must have required property 'modifier_payer_information'", - warning: enforce2025 ? undefined : true, - }, - ]) -}) - -test("validateJson minimum not required if there are no payer-specific standard charges", async (t) => { - const result = await validateJson( - loadFixtureStream("/2.0/sample-conditional-valid-minimum.json"), - "v2.0" - ) - t.is(result.valid, true) -}) diff --git a/test/common/csv.spec.ts b/test/common/csv.spec.ts deleted file mode 100644 index d9df1d3..0000000 --- a/test/common/csv.spec.ts +++ /dev/null @@ -1,17 +0,0 @@ -import test from "ava" -import { csvColumnName, matchesString } from "../../src/versions/common/csv.js" - -test("csvColumnName", (t) => { - t.is(csvColumnName(0), "A") - t.is(csvColumnName(26), "AA") - t.is(csvColumnName(702), "AAA") - t.is(csvColumnName(18278), "AAAA") - t.is(csvColumnName(475254), "AAAAA") -}) - -test("matchesString", (t) => { - t.is(matchesString("testing", "testing"), true) - t.is(matchesString("testing", "tresting"), false) - t.is(matchesString(1 as unknown as string, "testing"), false) - t.is(matchesString(undefined as unknown as string, "testing"), false) -}) diff --git a/test/csv.e2e.spec.ts b/test/csv.e2e.spec.ts deleted file mode 100644 index 3f5247a..0000000 --- a/test/csv.e2e.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -import test from "ava" -import { validateCsv } from "../src/csv.js" -import { loadFixtureStream } from "./utils.js" - -test("sample-1", async (t) => { - t.deepEqual( - (await validateCsv(loadFixtureStream("sample-1.csv"), "v1.1")).errors, - [ - { - field: "code | 1 | type", - message: - '"code | 1 | type" value "c" is not one of the allowed values: "CPT", "HCPCS", "ICD", "MS-DRG", "R-DRG", "S-DRG", "APS-DRG", "AP-DRG", "APR-DRG", "APC", "NDC", "HIPPS", "LOCAL", "EAPG", "CDT", "RC", "CDM"', - path: "C4", - warning: true, - }, - { - field: "setting", - message: - '"setting" value "ipatient" is not one of the allowed values: "inpatient", "outpatient", "both"', - path: "G4", - }, - { - field: "standard_charge | contracting_method", - message: - '"standard_charge | contracting_method" value "another" is not one of the allowed values: "case rate", "fee schedule", "percent of total billed charges", "per diem", "other"', - path: "row 4", - }, - { - field: "code | 1 | type", - message: - '"code | 1 | type" value "d" is not one of the allowed values: "CPT", "HCPCS", "ICD", "MS-DRG", "R-DRG", "S-DRG", "APS-DRG", "AP-DRG", "APR-DRG", "APC", "NDC", "HIPPS", "LOCAL", "EAPG", "CDT", "RC", "CDM"', - path: "C5", - warning: true, - }, - { - field: "setting", - message: - '"setting" value "opatient" is not one of the allowed values: "inpatient", "outpatient", "both"', - path: "G5", - }, - ] - ) -}) - -test("sample-1 maxErrors", async (t) => { - t.deepEqual( - ( - await validateCsv(loadFixtureStream("sample-1.csv"), "v1.1", { - maxErrors: 1, - }) - ).errors, - [ - { - field: "code | 1 | type", - message: - '"code | 1 | type" value "c" is not one of the allowed values: "CPT", "HCPCS", "ICD", "MS-DRG", "R-DRG", "S-DRG", "APS-DRG", "AP-DRG", "APR-DRG", "APC", "NDC", "HIPPS", "LOCAL", "EAPG", "CDT", "RC", "CDM"', - path: "C4", - warning: true, - }, - { - field: "setting", - message: - '"setting" value "ipatient" is not one of the allowed values: "inpatient", "outpatient", "both"', - path: "G4", - }, - ] - ) -}) - -test("sample-2", async (t) => { - t.deepEqual( - (await validateCsv(loadFixtureStream("sample-2.csv"), "v1.1")).errors, - [ - { - field: "setting", - message: - '"setting" value "ipatient" is not one of the allowed values: "inpatient", "outpatient", "both"', - path: "G4", - }, - ] - ) -}) diff --git a/test/filename.spec.ts b/test/filename.spec.ts deleted file mode 100644 index 559ce48..0000000 --- a/test/filename.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import test from "ava" -import { validateFilename } from "../src/filename.js" - -test("validateFilename", (t) => { - t.assert(!validateFilename("")) - t.assert(validateFilename("121234567_Example-Hospital_standardcharges.csv")) - t.assert( - validateFilename( - "12-1234567-1234567890_Example-Hospital_standardcharges.csv" - ) - ) - t.assert( - validateFilename( - "12-1234567-1234567890_Example-Hospital_standardcharges.json" - ) - ) - t.assert(!validateFilename("1212345678_example_standardcharges.xlsx")) - t.assert( - validateFilename( - "12-1234567-1234567890_Example Hospital_standardcharges.csv" - ) - ) -}) diff --git a/test/fixtures/1.1/sample-1.json b/test/fixtures/1.1/sample-1.json deleted file mode 100644 index 3d5a929..0000000 --- a/test/fixtures/1.1/sample-1.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "hospital_name": "Example Hospital", - "last_updated_on": "2023-01-01", - "license_information": { "license_number": "123456789", "state": "MD" }, - "version": "1.0.0", - "financial_aid_policy": "https://example.com", - "standard_charge_information": [ - { - "description": "MED 20MCG 1DOSE", - "billing_code_information": [ - { "code": "001A", "type": "CPT" }, - { "code": "1234", "type": "RC" } - ], - "standard_charges": [ - { - "minimum": 52.5, - "maximum": 125.2, - "gross_charge": 200.5, - "discounted_cash": 150.5, - "setting": "outpatient", - "payers_information": [ - { - "payer_name": "Payer 1", - "plan_name": "Plan | All Plans", - "standard_charge": 125.2, - "contracting_method": "fee schedule" - }, - { - "payer_name": "Payer 2", - "plan_name": "Commercial | All Plans", - "standard_charge": 52.5, - "contracting_method": "case rate" - } - ], - "billing_class": "facility" - } - ] - }, - { - "description": "HEART TRANSPLANT", - "billing_code_information": [{ "code": "999", "type": "MS-DRG" }], - "standard_charges": [ - { - "minimum": 200000.25, - "maximum": 500000.75, - "setting": "inpatient", - "payers_information": [ - { - "payer_name": "Payer 1", - "plan_name": "Plan | All Plans", - "standard_charge_percent": 70, - "contracting_method": "percent of total billed charges" - }, - { - "payer_name": "Payer 2", - "plan_name": "Commercial | All Plans", - "standard_charge": 500000.75, - "contracting_method": "case rate" - } - ], - "billing_class": "facility" - } - ] - } - ] -} diff --git a/test/fixtures/1.1/sample-valid.json b/test/fixtures/1.1/sample-valid.json deleted file mode 100644 index 6fa56c5..0000000 --- a/test/fixtures/1.1/sample-valid.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "hospital_name": "Test Hospital", - "last_updated_on": "2023-01-01", - "version": "1.1", - "standard_charge_information": [ - { - "description": "MRI", - "billing_code_information": [{ "code": "12345", "type": "CDM" }], - "standard_charges": [ - { - "setting": "outpatient", - "minimum": 300, - "maximum": 1500, - "gross_charge": 1500, - "discounted_cash": 1200, - "payers_information": [ - { - "payer_name": "Aetna", - "plan_name": "Commercial", - "contracting_method": "case rate", - "standard_charge": 1000 - }, - { - "payer_name": "Payer", - "plan_name": "PPO", - "contracting_method": "case rate", - "standard_charge": 1000 - } - ], - "billing_class": "facility" - } - ] - }, - { - "description": "Exam", - "billing_code_information": [ - { "code": "2345", "type": "CDM" }, - { "code": "123", "type": "RC" } - ], - "standard_charges": [ - { - "setting": "outpatient", - "minimum": 1, - "maximum": 50, - "gross_charge": 50, - "discounted_cash": 40, - "payers_information": [ - { - "payer_name": "Aetna", - "plan_name": "Commercial", - "contracting_method": "case rate", - "standard_charge": 25 - }, - { - "payer_name": "America", - "plan_name": "PPO", - "contracting_method": "case rate", - "standard_charge": 25 - } - ], - "billing_class": "facility" - } - ] - } - ] -} diff --git a/test/fixtures/2.0/error.csv b/test/fixtures/2.0/error.csv deleted file mode 100644 index 49a10f1..0000000 --- a/test/fixtures/2.0/error.csv +++ /dev/null @@ -1,13 +0,0 @@ -hospital_name,last_updated_on,version,hospital_location,hospital_address,license_number|AL,"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.",,,,,,,,,,,,,,,,,, -West Mercy Hospital,2024-07-01,2.0.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,TRUE,,,,,,,,,,,,,,,,,, -description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,standard_charge|Platform_Health_Insurance|PPO|negotiated_dollar,standard_charge|Platform_Health_Insurance|PPO|negotiated_percentage,standard_charge|Platform_Health_Insurance|PPO|negotiated_algorithm,estimated_amount|Platform_Health_Insurance|PPO,standard_charge|Platform_Health_Insurance|PPO|methodology,additional_payer_notes|Platform_Health_Insurance|PPO,standard_charge|Region_Health_Insurance|HMO|negotiated_dollar,standard_charge|Region_Health_Insurance|HMO|negotiated_percentage,standard_charge|Region_Health_Insurance|HMO|negotiated_algorithm,estimated_amount|Region_Health_Insurance|HMO,standard_charge|Region_Health_Insurance|HMO|methodology,additional_payer_notes|Region_Health_Insurance|HMO,standard_charge|min,standard_charge|max,additional_generic_notes -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,20000,,MS-DRG,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,20000,,The adjusted base rate indicated (in dollars) may be further adjusted for transfers and outliers.,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,150,125,98.98,,,,fee schedule,110% of the Medicare fee schedule,,115,,105.34,fee schedule,115% of the state's workers' compensation amount,98.98,98.98, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,2500,2250,1500,,,,per diem,,,,,,,,1500,1500, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,HCPCS,,,,inpatient,,2500,2250,,,,,,,2000,,,,per diem,,2000,2000, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,2500,2250,,,,,,,1800,,,,per diem,,1800,1800, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,2500,2250,,,,,,,1200,,,,per diem,,1200,1200, -Treatment or observation room — observation room,762,RC,,,,outpatient,,13000,12000,8000,,,,case rate,Negotiated standard charge without surgery and without rule out myocardial infarction,9000,,,,case rate,,8000,10000, -Treatment or observation room — observation room,762,RC,,,,outpatient,,13000,12000,10000,,,,case rate,Negotiated standard charge without surgery and with rule out myocardial infarction,,,,,,,8000,10000, \ No newline at end of file diff --git a/test/fixtures/2.0/sample-2025-properties.json b/test/fixtures/2.0/sample-2025-properties.json deleted file mode 100644 index f6c461c..0000000 --- a/test/fixtures/2.0/sample-2025-properties.json +++ /dev/null @@ -1,200 +0,0 @@ -{ - "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" - } - ], - "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" - } - ] - } - ], - "drug_information": { - "unit": "2.5" - } - }, - { - "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" - } - ] - } - ] - } - ], - "modifier_information": [ - { - "description": "some kind of modifier", - "code": "50" - } - ] -} diff --git a/test/fixtures/2.0/sample-conditional-error-estimate.json b/test/fixtures/2.0/sample-conditional-error-estimate.json deleted file mode 100644 index fe92636..0000000 --- a/test/fixtures/2.0/sample-conditional-error-estimate.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "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, - "methodology": "percent of total billed charges", - "additional_payer_notes": "this is missing an estimated_amount" - } - ] - } - ] - }, - { - "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" - } - ] - } - ] - } - ] -} diff --git a/test/fixtures/2.0/sample-conditional-error-minimum.json b/test/fixtures/2.0/sample-conditional-error-minimum.json deleted file mode 100644 index bbe4282..0000000 --- a/test/fixtures/2.0/sample-conditional-error-minimum.json +++ /dev/null @@ -1,194 +0,0 @@ -{ - "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, - "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": 2000, - "setting": "inpatient", - "payers_information": [ - { - "payer_name": "Platform Health Insurance", - "plan_name": "PPO", - "standard_charge_dollar": 115, - "methodology": "per diem" - }, - { - "payer_name": "Region Health Insurance", - "plan_name": "HMO", - "standard_charge_percentage": 115, - "estimated_amount": 22243.34, - "methodology": "per diem", - "additional_payer_notes": "per diem, days 1-3" - }, - { - "payer_name": "Region Health Insurance", - "plan_name": "HMO", - "standard_charge_percentage": 115, - "estimated_amount": 22243.34, - "methodology": "per diem", - "additional_payer_notes": "per diem, days 4-5" - }, - { - "payer_name": "Region Health Insurance", - "plan_name": "HMO", - "standard_charge_percentage": 115, - "estimated_amount": 22243.34, - "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, - "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" - } - ] - } - ] - } - ] -} diff --git a/test/fixtures/2.0/sample-conditional-error-ndc.json b/test/fixtures/2.0/sample-conditional-error-ndc.json deleted file mode 100644 index 435dea2..0000000 --- a/test/fixtures/2.0/sample-conditional-error-ndc.json +++ /dev/null @@ -1,195 +0,0 @@ -{ - "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": "NDC" - } - ], - "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" - } - ] - } - ] - } - ] -} diff --git a/test/fixtures/2.0/sample-conditional-error-standardcharge.json b/test/fixtures/2.0/sample-conditional-error-standardcharge.json deleted file mode 100644 index 1468197..0000000 --- a/test/fixtures/2.0/sample-conditional-error-standardcharge.json +++ /dev/null @@ -1,161 +0,0 @@ -{ - "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" - - } - ] - }, - { - "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", - "minimum": 98.98, - "maximum": 98.98, - "payers_information": [ - { - "payer_name": "Platform Health Insurance", - "plan_name": "PPO", - "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": [ - { - "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 diff --git a/test/fixtures/2.0/sample-conditional-errors.json b/test/fixtures/2.0/sample-conditional-errors.json deleted file mode 100644 index 75e1bf0..0000000 --- a/test/fixtures/2.0/sample-conditional-errors.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "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", - "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": "other" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/test/fixtures/2.0/sample-conditional-valid-minimum.json b/test/fixtures/2.0/sample-conditional-valid-minimum.json deleted file mode 100644 index 0877113..0000000 --- a/test/fixtures/2.0/sample-conditional-valid-minimum.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "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": [ - { - "setting": "inpatient", - "gross_charge": 1000 - } - ] - } - ] -} \ No newline at end of file diff --git a/test/fixtures/2.0/sample-empty.json b/test/fixtures/2.0/sample-empty.json deleted file mode 100644 index 0967ef4..0000000 --- a/test/fixtures/2.0/sample-empty.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/test/fixtures/2.0/sample-errors.json b/test/fixtures/2.0/sample-errors.json deleted file mode 100644 index a9fcb9f..0000000 --- a/test/fixtures/2.0/sample-errors.json +++ /dev/null @@ -1,196 +0,0 @@ -{ - "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 diff --git a/test/fixtures/2.0/sample-tall-valid-quoted.csv b/test/fixtures/2.0/sample-tall-valid-quoted.csv deleted file mode 100644 index e1c3bff..0000000 --- a/test/fixtures/2.0/sample-tall-valid-quoted.csv +++ /dev/null @@ -1,16 +0,0 @@ -"hospital_name",last_updated_on,version,hospital_location,hospital_address,license_number|CA,"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.",,,,,,,,,,,,,, -West Mercy Hospital,2024-07-01,2.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,true,,,,,,,,,,,,,, -Description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_unit_of_measurement,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,payer_name,plan_name,standard_charge|negotiated_dollar,standard_charge|negotiated_percentage,standard_charge|negotiated_algorithm,estimated_amount,standard_charge|methodology,standard_charge|min,standard_charge|max,additional_generic_notes -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Platform Health Insurance,PPO,20000,,MS-DRG,22243.34,case rate,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Platform Health Insurance,PPO,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Platform Health Insurance,PPO,20000,,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.,22243.34,case rate,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Region Health Insurance,HMO,,50,,23145.98,percent of total billed charges,20000,20000, -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,Platform Health Insurance,PPO,98.98,,,,fee schedule,98.98,98.98,110% of the Medicare fee schedule -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,Region Health Insurance,HMO,,115,,105.34,fee schedule,98.98,98.98,115% of the state's workers' compensation amount -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,,2500,2250,Platform Health Insurance,PPO,1500,,,,per diem,1500,1500, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,hcpcs,,,,inpatient,,,2500,2250,Region Health Insurance,HMO,2000,,,,per diem,2000,2000, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,,2500,2250,Region Health Insurance,HMO,1800,,,,per diem,1800,1800, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,,2500,2250,Region Health Insurance,HMO,1200,,,,per diem,1200,1200, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,Platform Health Insurance,PPO,8000,,,,case rate,8000,10000,Negotiated standard charge without surgery and without rule out myocardial infarction -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,Platform Health Insurance,PPO,10000,,,,case rate,8000,10000,Negotiated standard charge without surgery and with rule out myocardial infarction -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,Region Health Insurance,HMO,9000,,,,case rate,8000,10000, diff --git a/test/fixtures/2.0/sample-wide-error-bad-enum.csv b/test/fixtures/2.0/sample-wide-error-bad-enum.csv deleted file mode 100644 index b628aff..0000000 --- a/test/fixtures/2.0/sample-wide-error-bad-enum.csv +++ /dev/null @@ -1,13 +0,0 @@ -hospital_name,last_updated_on,version,hospital_location,hospital_address,license_number|AL,"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.",,,,,,,,,,,,,,,,,,, -West Mercy Hospital,2024-07-01,2.0.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,true,,,,,,,,,,,,,,,,,,, -description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_unit_of_measurement,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,standard_charge|Platform_Health_Insurance|PPO|negotiated_dollar,standard_charge|Platform_Health_Insurance|PPO|negotiated_percentage,standard_charge|Platform_Health_Insurance|PPO|negotiated_algorithm,estimated_amount|Platform_Health_Insurance|PPO,standard_charge|Platform_Health_Insurance|PPO|methodology,additional_payer_notes|Platform_Health_Insurance|PPO,standard_charge|Region_Health_Insurance|HMO|negotiated_dollar,standard_charge|Region_Health_Insurance|HMO|negotiated_percentage,standard_charge|Region_Health_Insurance|HMO|negotiated_algorithm,estimated_amount|Region_Health_Insurance|HMO,standard_charge|Region_Health_Insurance|HMO|methodology,additional_payer_notes|Region_Health_Insurance|HMO,standard_charge|min,standard_charge|max,additional_generic_notes -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRGG,175869,LOCAL,,inpatient,,,,,20000,,MS-DRG,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,The adjusted base rate indicated (in dollars) may be further adjusted for transfers and outliers.,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,98.98,,,,fee schedule,110% of the Medicare fee schedule,,115,,105.34,fee schedule,115% of the state's workers' compensation amount,98.98,98.98, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,,2500,2250,1500,,,,per diem,,,,,,,,1500,1500, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,2000,,,,per diem,,2000,2000, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1800,,,,per diem,,1800,1800, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1200,,,,per diem,,1200,1200, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,8000,,,,case rate,Negotiated standard charge without surgery and without rule out myocardial infarction,9000,,,,case rate,,8000,10000, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,10000,,,,case rate,Negotiated standard charge without surgery and with rule out myocardial infarction,,,,,,,8000,10000, diff --git a/test/fixtures/2.0/sample-wide-error-header-standardcharge.csv b/test/fixtures/2.0/sample-wide-error-header-standardcharge.csv deleted file mode 100644 index f1d3594..0000000 --- a/test/fixtures/2.0/sample-wide-error-header-standardcharge.csv +++ /dev/null @@ -1,13 +0,0 @@ -hospital_name,last_updated_on,version,hospital_location,hospital_address,license_number|CA,"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.",,,,,,,,,,,,,,,,,,, -West Mercy Hospital,2024-07-01,2.0.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,true,,,,,,,,,,,,,,,,,,, -description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_unit_of_measurement,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,standard_charge|Platform_Health_Insurance|PPO|negotiated_dollarr,standard_charge|Platform_Health_Insurance|PPO|negotiated_percentage,standard_charge|Platform_Health_Insurance|PPO|negotiated_algorithm,estimated_amount|Platform_Health_Insurance|PPO,standard_charge|Platform_Health_Insurance|PPO|methodology,additional_payer_notes|Platform_Health_Insurance|PPO,standard_charge|Region_Health_Insurance|HMO|negotiated_dollar,standard_charge|Region_Health_Insurance|HMO|negotiated_percentage,standard_charge|Region_Health_Insurance|HMO|negotiated_algorithm,estimated_amount|Region_Health_Insurance|HMO,standard_charge|Region_Health_Insurance|HMO|methodology,additional_payer_notes|Region_Health_Insurance|HMO,standard_charge|min,standard_charge|max,additional_generic_notes -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,MS-DRG,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,The adjusted base rate indicated (in dollars) may be further adjusted for transfers and outliers.,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,98.98,,,,fee schedule,110% of the Medicare fee schedule,,115,,105.34,fee schedule,115% of the state's workers' compensation amount,98.98,98.98, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,,2500,2250,1500,,,,per diem,,,,,,,,1500,1500, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,2000,,,,per diem,,2000,2000, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1800,,,,per diem,,1800,1800, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1200,,,,per diem,,1200,1200, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,8000,,,,case rate,Negotiated standard charge without surgery and without rule out myocardial infarction,9000,,,,case rate,,8000,10000, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,10000,,,,case rate,Negotiated standard charge without surgery and with rule out myocardial infarction,,,,,,,8000,10000, diff --git a/test/fixtures/2.0/sample-wide-error-header.csv b/test/fixtures/2.0/sample-wide-error-header.csv deleted file mode 100644 index 543b97e..0000000 --- a/test/fixtures/2.0/sample-wide-error-header.csv +++ /dev/null @@ -1,13 +0,0 @@ -hospital_name,last_updated_on,version,hospital_ location,hospital_address,license_number|AL,"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.",,,,,,,,,,,,,,,,,,, -West Mercy Hospital,2024-07-01,2.0.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,yes,,,,,,,,,,,,,,,,,,, -description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_unit_of_measurement,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,standard_charge|Platform_Health_Insurance|PPO|negotiated_dollar,standard_charge|Platform_Health_Insurance|PPO|negotiated_percentage,standard_charge|Platform_Health_Insurance|PPO|negotiated_algorithm,estimated_amount|Platform_Health_Insurance|PPO,standard_charge|Platform_Health_Insurance|PPO|methodology,additional_payer_notes|Platform_Health_Insurance|PPO,standard_charge|Region_Health_Insurance|HMO|negotiated_dollar,standard_charge|Region_Health_Insurance|HMO|negotiated_percentage,standard_charge|Region_Health_Insurance|HMO|negotiated_algorithm,estimated_amount|Region_Health_Insurance|HMO,standard_charge|Region_Health_Insurance|HMO|methodology,additional_payer_notes|Region_Health_Insurance|HMO,standard_charge|min,standard_charge|max,additional_generic_notes -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,MS-DRG,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,The adjusted base rate indicated (in dollars) may be further adjusted for transfers and outliers.,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,98.98,,,,fee schedule,110% of the Medicare fee schedule,,115,,105.34,fee schedule,115% of the state's workers' compensation amount,98.98,98.98, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,,2500,2250,1500,,,,per diem,,,,,,,,1500,1500, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,2000,,,,per diem,,2000,2000, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1800,,,,per diem,,1800,1800, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1200,,,,per diem,,1200,1200, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,8000,,,,case rate,Negotiated standard charge without surgery and without rule out myocardial infarction,9000,,,,case rate,,8000,10000, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,10000,,,,case rate,Negotiated standard charge without surgery and with rule out myocardial infarction,,,,,,,8000,10000, diff --git a/test/fixtures/2.0/sample-wide-error-missing-value.csv b/test/fixtures/2.0/sample-wide-error-missing-value.csv deleted file mode 100644 index 772737a..0000000 --- a/test/fixtures/2.0/sample-wide-error-missing-value.csv +++ /dev/null @@ -1,13 +0,0 @@ -hospital_name,last_updated_on,version,hospital_location,hospital_address,license_number|AL,"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.",,,,,,,,,,,,,,,,,,, -West Mercy Hospital,2024-07-01,2.0.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,true,,,,,,,,,,,,,,,,,,, -description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_unit_of_measurement,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,standard_charge|Platform_Health_Insurance|PPO|negotiated_dollar,standard_charge|Platform_Health_Insurance|PPO|negotiated_percentage,standard_charge|Platform_Health_Insurance|PPO|negotiated_algorithm,estimated_amount|Platform_Health_Insurance|PPO,standard_charge|Platform_Health_Insurance|PPO|methodology,additional_payer_notes|Platform_Health_Insurance|PPO,standard_charge|Region_Health_Insurance|HMO|negotiated_dollar,standard_charge|Region_Health_Insurance|HMO|negotiated_percentage,standard_charge|Region_Health_Insurance|HMO|negotiated_algorithm,estimated_amount|Region_Health_Insurance|HMO,standard_charge|Region_Health_Insurance|HMO|methodology,additional_payer_notes|Region_Health_Insurance|HMO,standard_charge|min,standard_charge|max,additional_generic_notes -Major hip and knee joint replacement or reattachment of lower extremity without mcc,,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,MS-DRG,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,The adjusted base rate indicated (in dollars) may be further adjusted for transfers and outliers.,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,98.98,,,,fee schedule,110% of the Medicare fee schedule,,115,,105.34,fee schedule,115% of the state's workers' compensation amount,98.98,98.98, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,,2500,2250,1500,,,,per diem,,,,,,,,1500,1500, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,2000,,,,per diem,,2000,2000, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1800,,,,per diem,,1800,1800, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1200,,,,per diem,,1200,1200, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,8000,,,,case rate,Negotiated standard charge without surgery and without rule out myocardial infarction,9000,,,,case rate,,8000,10000, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,10000,,,,case rate,Negotiated standard charge without surgery and with rule out myocardial infarction,,,,,,,8000,10000, diff --git a/test/fixtures/2.0/sample-wide-error.csv b/test/fixtures/2.0/sample-wide-error.csv deleted file mode 100644 index 57a7213..0000000 --- a/test/fixtures/2.0/sample-wide-error.csv +++ /dev/null @@ -1,13 +0,0 @@ -hospital_name,last_updated_on,version,hospital_location,hospital_address,license_number|AL,"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.",,,,,,,,,,,,,,,,,,, -West Mercy Hospital,2024-07-01,2.0.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,true,,,,,,,,,,,,,,,,,,, -description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_unit_of_measurement,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,standard_charge|Platform_Health_Insurance|PPO|negotiated_dollar,standard_charge|Platform_Health_Insurance|PPO|negotiated_percentage,standard_charge|Platform_Health_Insurance|PPO|negotiated_algorithm,estimated_amount|Platform_Health_Insurance|PPO,standard_charge|Platform_Health_Insurance|PPO|methodology,additional_payer_notes|Platform_Health_Insurance|PPO,standard_charge|Region_Health_Insurance|HMO|negotiated_dollar,standard_charge|Region_Health_Insurance|HMO|negotiated_percentage,standard_charge|Region_Health_Insurance|HMO|negotiated_algorithm,estimated_amount|Region_Health_Insurance|HMO,standard_charge|Region_Health_Insurance|HMO|methodology,additional_payer_notes|Region_Health_Insurance|HMO,standard_charge|min,standard_charge|max,additional_generic_notes -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,mS-DRG,175869,LOCAL,,inpatient,,,,,20000,,MS-DRG,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,The adjusted base rate indicated (in dollars) may be further adjusted for transfers and outliers.,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,98.98,,,,fee schedule,110% of the Medicare fee schedule,,115,,105.34,fee schedule,115% of the state's workers' compensation amount,98.98,98.98, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,,2500,2250,1500,,,,per diem,,,,,,,,1500,1500, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,2000,,,,per diem,,2000,2000, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1800,,,,per diem,,1800,1800, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1200,,,,per diem,,1200,1200, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,8000,,,,case rate,Negotiated standard charge without surgery and without rule out myocardial infarction,9000,,,,case rate,,8000,10000, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,10000,,,,case rate,Negotiated standard charge without surgery and with rule out myocardial infarction,,,,,,,8000,10000, diff --git a/test/fixtures/2.0/sample-wide-header-empty.csv b/test/fixtures/2.0/sample-wide-header-empty.csv deleted file mode 100644 index 8526690..0000000 --- a/test/fixtures/2.0/sample-wide-header-empty.csv +++ /dev/null @@ -1,13 +0,0 @@ -hospital_name,last_updated_on,version,hospital_location,hospital_address,license_number|AL,"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.",,,,,,,,,,,,,,,,,,, -,,,,,,,,,,,,,,,,,,,,,,,, -description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_unit_of_measurement,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,standard_charge|Platform_Health_Insurance|PPO|negotiated_dollar,standard_charge|Platform_Health_Insurance|PPO|negotiated_percentage,standard_charge|Platform_Health_Insurance|PPO|negotiated_algorithm,estimated_amount|Platform_Health_Insurance|PPO,standard_charge|Platform_Health_Insurance|PPO|methodology,additional_payer_notes|Platform_Health_Insurance|PPO,standard_charge|Region_Health_Insurance|HMO|negotiated_dollar,standard_charge|Region_Health_Insurance|HMO|negotiated_percentage,standard_charge|Region_Health_Insurance|HMO|negotiated_algorithm,estimated_amount|Region_Health_Insurance|HMO,standard_charge|Region_Health_Insurance|HMO|methodology,additional_payer_notes|Region_Health_Insurance|HMO,standard_charge|min,standard_charge|max,additional_generic_notes -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,MS-DRG,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,The adjusted base rate indicated (in dollars) may be further adjusted for transfers and outliers.,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,98.98,,,,fee schedule,110% of the Medicare fee schedule,,115,,105.34,fee schedule,115% of the state's workers' compensation amount,98.98,98.98, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,,2500,2250,1500,,,,per diem,,,,,,,,1500,1500, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,2000,,,,per diem,,2000,2000, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1800,,,,per diem,,1800,1800, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1200,,,,per diem,,1200,1200, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,8000,,,,case rate,Negotiated standard charge without surgery and without rule out myocardial infarction,9000,,,,case rate,,8000,10000, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,10000,,,,case rate,Negotiated standard charge without surgery and with rule out myocardial infarction,,,,,,,8000,10000, diff --git a/test/fixtures/2.0/sample-wide-missing-new-columns.csv b/test/fixtures/2.0/sample-wide-missing-new-columns.csv deleted file mode 100644 index cc0a7aa..0000000 --- a/test/fixtures/2.0/sample-wide-missing-new-columns.csv +++ /dev/null @@ -1,13 +0,0 @@ -hospital_name,last_updated_on,version,hospital_location,hospital_address,license_number|CA,"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.",,,,,,,,,,,,,,,,, -West Mercy Hospital,2024-07-01,2.0.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,TRUE,,,,,,,,,,,,,,,,, -description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,standard_charge|gross,standard_charge|discounted_cash,standard_charge|Platform_Health_Insurance|PPO|negotiated_dollar,standard_charge|Platform_Health_Insurance|PPO|negotiated_percentage,standard_charge|Platform_Health_Insurance|PPO|negotiated_algorithm,estimated_amount|Platform_Health_Insurance|PPO,standard_charge|Platform_Health_Insurance|PPO|methodology,additional_payer_notes|Platform_Health_Insurance|PPO,standard_charge|Region_Health_Insurance|HMO|negotiated_dollar,standard_charge|Region_Health_Insurance|HMO|negotiated_percentage,standard_charge|Region_Health_Insurance|HMO|negotiated_algorithm,estimated_amount|Region_Health_Insurance|HMO,standard_charge|Region_Health_Insurance|HMO|methodology,additional_payer_notes|Region_Health_Insurance|HMO,standard_charge|min,standard_charge|max,additional_generic_notes -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,20000,,MS-DRG,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,20000,,The adjusted base rate indicated (in dollars) may be further adjusted for transfers and outliers.,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,150,125,98.98,,,,fee schedule,110% of the Medicare fee schedule,,115,,105.34,fee schedule,115% of the state's workers' compensation amount,98.98,98.98, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,2500,2250,1500,,,,per diem,,,,,,,,1500,1500, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,HCPCS,,,,inpatient,2500,2250,,,,,,,2000,,,,per diem,,2000,2000, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,2500,2250,,,,,,,1800,,,,per diem,,1800,1800, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,2500,2250,,,,,,,1200,,,,per diem,,1200,1200, -Treatment or observation room — observation room,762,RC,,,,outpatient,13000,12000,8000,,,,case rate,Negotiated standard charge without surgery and without rule out myocardial infarction,9000,,,,case rate,,8000,10000, -Treatment or observation room — observation room,762,RC,,,,outpatient,13000,12000,10000,,,,case rate,Negotiated standard charge without surgery and with rule out myocardial infarction,,,,,,,8000,10000, diff --git a/test/fixtures/sample-1.csv b/test/fixtures/sample-1.csv deleted file mode 100644 index 0e7b874..0000000 --- a/test/fixtures/sample-1.csv +++ /dev/null @@ -1,5 +0,0 @@ -Hospital_name,last_updated_on,version,hospital_location,financial_aid_policy,license_number | MD,,,,,,,,,,,,,, -Example Hospital,2/1/23,1.0.0,Woodlawn,See aid policy on site,12345678,,,,,,,,,,,,,, -description,code | 1,code | 1 | type,code | 2,code | 2 | type,billing_class,setting,drug_unit_of_measurement,drug_type_of_measurement,modifiers,standard_charge | gross,standard_charge | discounted_cash,payer_name,plan_name,standard_charge | negotiated_dollar,standard_charge | negotiated_percent,standard_charge | min,standard_charge | max,standard_charge | contracting_method,additional_generic_notes -Code 1,A123,C,,,Facility,ipatient,1,ML,,100,90,Payer,Plan,80,20,60,80,another, -Code 1,A456,D,,,Facility,opatient,1,ML,,100,90,Payer,Plan,80,20,60,80,other, \ No newline at end of file diff --git a/test/fixtures/sample-2.csv b/test/fixtures/sample-2.csv deleted file mode 100644 index 665e176..0000000 --- a/test/fixtures/sample-2.csv +++ /dev/null @@ -1,4 +0,0 @@ -hospital_name,last_updated_on,version,hospital_location,financial_aid_policy,license_number | MD,,,,,,,,,,,,,, -Example Hospital,2/1/23,1.0.0,Woodlawn,https://example.com/,12345678,,,,,,,,,,,,,, -description,code | 1,code | 1 | type,code | 2,code | 2 | type,billing_class,setting,drug_unit_of_measurement,drug_type_of_measurement,modifiers,standard_charge | gross,standard_charge | discounted_cash,payer_name,plan_name,standard_charge | negotiated_dollar,standard_charge | negotiated_percent,standard_charge | min,standard_charge | max,standard_charge | contracting_method,additional_generic_notes -Code 1,A123,CPT,,,Facility,ipatient,1,ML,,100,90,Payer,Plan,80,20,60,80,other, \ No newline at end of file diff --git a/test/fixtures/1.1/sample-empty.json b/test/fixtures/sample-empty.json similarity index 100% rename from test/fixtures/1.1/sample-empty.json rename to test/fixtures/sample-empty.json diff --git a/test/fixtures/2.0/sample-invalid.json b/test/fixtures/sample-invalid.json similarity index 100% rename from test/fixtures/2.0/sample-invalid.json rename to test/fixtures/sample-invalid.json diff --git a/test/fixtures/2.0/sample-tall-valid.csv b/test/fixtures/sample-tall-valid.csv similarity index 99% rename from test/fixtures/2.0/sample-tall-valid.csv rename to test/fixtures/sample-tall-valid.csv index ee5abfe..73580ee 100644 --- a/test/fixtures/2.0/sample-tall-valid.csv +++ b/test/fixtures/sample-tall-valid.csv @@ -1,16 +1,16 @@ -hospital_name,last_updated_on,version,hospital_location,hospital_address,license_number|CA,"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.",,,,,,,,,,,,,, -West Mercy Hospital,2024-07-01,2.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,true,,,,,,,,,,,,,, -Description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_unit_of_measurement,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,payer_name,plan_name,standard_charge|negotiated_dollar,standard_charge|negotiated_percentage,standard_charge|negotiated_algorithm,estimated_amount,standard_charge|methodology,standard_charge|min,standard_charge|max,additional_generic_notes -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Platform Health Insurance,PPO,20000,,MS-DRG,22243.34,case rate,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Platform Health Insurance,PPO,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Platform Health Insurance,PPO,20000,,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.,22243.34,case rate,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Region Health Insurance,HMO,,50,,23145.98,percent of total billed charges,20000,20000, -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,Platform Health Insurance,PPO,98.98,,,,fee schedule,98.98,98.98,110% of the Medicare fee schedule -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,Region Health Insurance,HMO,,115,,105.34,fee schedule,98.98,98.98,115% of the state's workers' compensation amount -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,,2500,2250,Platform Health Insurance,PPO,1500,,,,per diem,1500,1500, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,hcpcs,,,,inpatient,,,2500,2250,Region Health Insurance,HMO,2000,,,,per diem,2000,2000, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,,2500,2250,Region Health Insurance,HMO,1800,,,,per diem,1800,1800, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,,2500,2250,Region Health Insurance,HMO,1200,,,,per diem,1200,1200, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,Platform Health Insurance,PPO,8000,,,,case rate,8000,10000,Negotiated standard charge without surgery and without rule out myocardial infarction -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,Platform Health Insurance,PPO,10000,,,,case rate,8000,10000,Negotiated standard charge without surgery and with rule out myocardial infarction -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,Region Health Insurance,HMO,9000,,,,case rate,8000,10000, +hospital_name,last_updated_on,version,hospital_location,hospital_address,license_number|CA,"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.",,,,,,,,,,,,,, +West Mercy Hospital,2024-07-01,2.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,true,,,,,,,,,,,,,, +Description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_unit_of_measurement,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,payer_name,plan_name,standard_charge|negotiated_dollar,standard_charge|negotiated_percentage,standard_charge|negotiated_algorithm,estimated_amount,standard_charge|methodology,standard_charge|min,standard_charge|max,additional_generic_notes +Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Platform Health Insurance,PPO,20000,,MS-DRG,22243.34,case rate,20000,20000, +Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Platform Health Insurance,PPO,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,20000,20000, +Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Platform Health Insurance,PPO,20000,,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.,22243.34,case rate,20000,20000, +Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,Region Health Insurance,HMO,,50,,23145.98,percent of total billed charges,20000,20000, +"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,Platform Health Insurance,PPO,98.98,,,,fee schedule,98.98,98.98,110% of the Medicare fee schedule +"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,Region Health Insurance,HMO,,115,,105.34,fee schedule,98.98,98.98,115% of the state's workers' compensation amount +"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,,2500,2250,Platform Health Insurance,PPO,1500,,,,per diem,1500,1500, +"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,hcpcs,,,,inpatient,,,2500,2250,Region Health Insurance,HMO,2000,,,,per diem,2000,2000, +"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,,2500,2250,Region Health Insurance,HMO,1800,,,,per diem,1800,1800, +"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,,2500,2250,Region Health Insurance,HMO,1200,,,,per diem,1200,1200, +Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,Platform Health Insurance,PPO,8000,,,,case rate,8000,10000,Negotiated standard charge without surgery and without rule out myocardial infarction +Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,Platform Health Insurance,PPO,10000,,,,case rate,8000,10000,Negotiated standard charge without surgery and with rule out myocardial infarction +Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,Region Health Insurance,HMO,9000,,,,case rate,8000,10000, diff --git a/test/fixtures/2.0/sample-valid-bom.json b/test/fixtures/sample-valid-bom.json similarity index 100% rename from test/fixtures/2.0/sample-valid-bom.json rename to test/fixtures/sample-valid-bom.json diff --git a/test/fixtures/2.0/sample-valid.json b/test/fixtures/sample-valid.json similarity index 100% rename from test/fixtures/2.0/sample-valid.json rename to test/fixtures/sample-valid.json diff --git a/test/fixtures/2.0/sample-wide-valid.csv b/test/fixtures/sample-wide-valid.csv similarity index 99% rename from test/fixtures/2.0/sample-wide-valid.csv rename to test/fixtures/sample-wide-valid.csv index 689f34b..7db966b 100644 --- a/test/fixtures/2.0/sample-wide-valid.csv +++ b/test/fixtures/sample-wide-valid.csv @@ -1,13 +1,13 @@ -hospital_name,last_updated_on,version,hospital_location,hospital_address,license_number|CA,"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.",,,,,,,,,,,,,,,,,,, -West Mercy Hospital,2024-07-01,2.0.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,true,,,,,,,,,,,,,,,,,,, -description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_unit_of_measurement,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,standard_charge|Platform_Health_Insurance|PPO|negotiated_dollar,standard_charge|Platform_Health_Insurance|PPO|negotiated_percentage,standard_charge|Platform_Health_Insurance|PPO|negotiated_algorithm,estimated_amount|Platform_Health_Insurance|PPO,standard_charge|Platform_Health_Insurance|PPO|methodology,additional_payer_notes|Platform_Health_Insurance|PPO,standard_charge|Region_Health_Insurance|HMO|negotiated_dollar,standard_charge|Region_Health_Insurance|HMO|negotiated_percentage,standard_charge|Region_Health_Insurance|HMO|negotiated_algorithm,estimated_amount|Region_Health_Insurance|HMO,standard_charge|Region_Health_Insurance|HMO|methodology,additional_payer_notes|Region_Health_Insurance|HMO,standard_charge|min,standard_charge|max,additional_generic_notes -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,MS-DRG,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,The adjusted base rate indicated (in dollars) may be further adjusted for transfers and outliers.,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, -"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,98.98,,,,fee schedule,110% of the Medicare fee schedule,,115,,105.34,fee schedule,115% of the state's workers' compensation amount,98.98,98.98, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,,2500,2250,1500,,,,per diem,,,,,,,,1500,1500, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,2000,,,,per diem,,2000,2000, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1800,,,,per diem,,1800,1800, -"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1200,,,,per diem,,1200,1200, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,8000,,,,case rate,Negotiated standard charge without surgery and without rule out myocardial infarction,9000,,,,case rate,,8000,10000, -Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,10000,,,,case rate,Negotiated standard charge without surgery and with rule out myocardial infarction,,,,,,,8000,10000, +hospital_name,last_updated_on,version,hospital_location,hospital_address,license_number|CA,"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.",,,,,,,,,,,,,,,,,,, +West Mercy Hospital,2024-07-01,2.0.0,West Mercy Hospital|West Mercy Surgical Center,"12 Main Street, Fullerton, CA 92832|23 Ocean Ave, San Jose, CA 94088",50056,true,,,,,,,,,,,,,,,,,,, +description,code|1,code|1|type,code|2,code|2|type,modifiers,setting,drug_unit_of_measurement,drug_type_of_measurement,standard_charge|gross,standard_charge|discounted_cash,standard_charge|Platform_Health_Insurance|PPO|negotiated_dollar,standard_charge|Platform_Health_Insurance|PPO|negotiated_percentage,standard_charge|Platform_Health_Insurance|PPO|negotiated_algorithm,estimated_amount|Platform_Health_Insurance|PPO,standard_charge|Platform_Health_Insurance|PPO|methodology,additional_payer_notes|Platform_Health_Insurance|PPO,standard_charge|Region_Health_Insurance|HMO|negotiated_dollar,standard_charge|Region_Health_Insurance|HMO|negotiated_percentage,standard_charge|Region_Health_Insurance|HMO|negotiated_algorithm,estimated_amount|Region_Health_Insurance|HMO,standard_charge|Region_Health_Insurance|HMO|methodology,additional_payer_notes|Region_Health_Insurance|HMO,standard_charge|min,standard_charge|max,additional_generic_notes +Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,MS-DRG,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, +Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,https://www.cms.gov/Outreach-and-Education/Medicare-Learning-Network-MLN/MLNProducts/html/images/OP.jpg,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, +Major hip and knee joint replacement or reattachment of lower extremity without mcc,470,MS-DRG,175869,LOCAL,,inpatient,,,,,20000,,The adjusted base rate indicated (in dollars) may be further adjusted for transfers and outliers.,22243.34,case rate,,,50,,23145.98,percent of total billed charges,,20000,20000, +"Evaluation of hearing function to determine candidacy for, or postoperative status of, surgically implanted hearing device; first hour",92626,CPT,,,,outpatient,,,150,125,98.98,,,,fee schedule,110% of the Medicare fee schedule,,115,,105.34,fee schedule,115% of the state's workers' compensation amount,98.98,98.98, +"Behavioral health; residential (hospital residential treatment program), without room and board, per diem",H0017,HCPCS,,,,inpatient,,,2500,2250,1500,,,,per diem,,,,,,,,1500,1500, +"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 1-3",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,2000,,,,per diem,,2000,2000, +"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 4-5",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1800,,,,per diem,,1800,1800, +"Behavioral health; residential (hospital residential treatment program), without room and board, per diem, days 6+",H0017,HCPCS,,,,inpatient,,,2500,2250,,,,,,,1200,,,,per diem,,1200,1200, +Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,8000,,,,case rate,Negotiated standard charge without surgery and without rule out myocardial infarction,9000,,,,case rate,,8000,10000, +Treatment or observation room — observation room,762,RC,,,,outpatient,,,13000,12000,10000,,,,case rate,Negotiated standard charge without surgery and with rule out myocardial infarction,,,,,,,8000,10000, diff --git a/test/testhelpers/createFixtureStream.ts b/test/testhelpers/createFixtureStream.ts new file mode 100644 index 0000000..de16fde --- /dev/null +++ b/test/testhelpers/createFixtureStream.ts @@ -0,0 +1,9 @@ +import * as fs from "fs"; +import * as path from "path"; + +export function createFixtureStream(fixture: string) { + return fs.createReadStream(path.join(__dirname, "..", "fixtures", fixture), { + encoding: "utf-8", + highWaterMark: 1024, + }); +} diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..43e9b46 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../tsconfig", + "compilerOptions": { + "strictNullChecks": false + }, + "include": [ + "./**/*.ts" + ] +} \ No newline at end of file diff --git a/test/utils.ts b/test/utils.ts deleted file mode 100644 index 6053dd9..0000000 --- a/test/utils.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as fs from "fs" -import * as path from "path" -import * as url from "url" -const __dirname = url.fileURLToPath(new URL(".", import.meta.url)) - -export function loadFixtureStream(name: string): fs.ReadStream { - return fs.createReadStream(path.join(__dirname, "fixtures", name), { - encoding: "utf-8", - highWaterMark: 1024, - }) -} diff --git a/test/validators/CsvValidator.test.ts b/test/validators/CsvValidator.test.ts new file mode 100644 index 0000000..197ae4d --- /dev/null +++ b/test/validators/CsvValidator.test.ts @@ -0,0 +1,653 @@ +import * as fs from "fs"; +import * as path from "path"; +import { AFFIRMATION, CsvValidator } from "../../src/validators/CsvValidator"; +import { + AllowedValuesError, + DuplicateHeaderColumnError, + HeaderColumnMissingError, + InvalidDateError, + InvalidStateCodeError, + RequiredValueError, +} from "../../src/errors/csv"; +import { shuffle } from "lodash"; + +describe("CsvValidator", () => { + describe("constructor", () => {}); + + describe("#reset", () => {}); + + describe("schema v2.0.0", () => {}); + + describe("schema v2.1.0", () => {}); + + describe("schema v2.2.0", () => { + let validator: CsvValidator; + + beforeAll(() => { + validator = new CsvValidator("v2.2.0"); + }); + + beforeEach(() => { + validator.reset(); + }); + + describe("#validate", () => { + it("should validate a valid tall CSV file", async () => { + const input = fs.createReadStream( + path.join(__dirname, "..", "fixtures", "sample-tall-valid.csv") + ); + const result = await validator.validate(input); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it("should validate a valid wide CSV file", async () => { + const input = fs.createReadStream( + path.join(__dirname, "..", "fixtures", "sample-wide-valid.csv") + ); + const result = await validator.validate(input); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + }); + + describe("#validateHeaderColumns", () => { + it("should return no errors when valid header columns are provided", () => { + const columns = shuffle([ + "hospital_name", + "last_updated_on", + "version", + "hospital_location", + "hospital_address", + "license_number | MD", + AFFIRMATION, + ]); + const result = validator.validateHeaderColumns(columns); + expect(result).toHaveLength(0); + expect(validator.headerColumns).toEqual(columns); + }); + + it("should return errors when required header columns are missing", () => { + const result = validator.validateHeaderColumns([]); + expect(result).toHaveLength(7); + expect(result[0]).toBeInstanceOf(HeaderColumnMissingError); + expect((result[0] as HeaderColumnMissingError).columnName).toBe( + "hospital_name" + ); + }); + + it("should return errors and remove duplicates when a header column appears more than once", () => { + const columns = [ + "hospital_name", + "last_updated_on", + "version", + "hospital_location", + "hospital_address", + "hospital_location", + "license_number | MD", + AFFIRMATION, + ]; + const result = validator.validateHeaderColumns(columns); + expect(result).toHaveLength(1); + expect(result[0]).toBeInstanceOf(DuplicateHeaderColumnError); + expect((result[0] as DuplicateHeaderColumnError).columnName).toBe( + "hospital_location" + ); + expect(validator.headerColumns).toEqual([ + "hospital_name", + "last_updated_on", + "version", + "hospital_location", + "hospital_address", + undefined, + "license_number | MD", + AFFIRMATION, + ]); + }); + + it("should return an error when the license number column contains an invalid state abbreviation", () => { + const columns = [ + "hospital_name", + "last_updated_on", + "version", + "hospital_location", + "hospital_address", + "license_number | XYZZY", + AFFIRMATION, + ]; + const result = validator.validateHeaderColumns(columns); + expect(result).toHaveLength(2); + expect(result).toContainEqual(new InvalidStateCodeError(5, "XYZZY")); + expect(result).toContainEqual( + new HeaderColumnMissingError("license_number | [state]") + ); + expect(validator.headerColumns).toEqual([ + "hospital_name", + "last_updated_on", + "version", + "hospital_location", + "hospital_address", + undefined, + AFFIRMATION, + ]); + }); + }); + + describe("#validateHeaderRow", () => { + const headerColumns = [ + "hospital_name", + "last_updated_on", + "version", + "hospital_location", + "hospital_address", + "license_number | MD", + AFFIRMATION, + ]; + + beforeEach(() => { + validator.headerColumns = [...headerColumns]; + }); + + it("should return no errors for valid header row values", () => { + const result = validator.validateHeaderRow([ + "name", + "2022-01-01", + "1.0.0", + "Woodlawn", + "123 Address", + "001 | MD", + "true", + ]); + expect(result).toHaveLength(0); + }); + + it("should return no errors for header row values regardless of capitalization for state code or affirmation value", () => { + const result = validator.validateHeaderRow([ + "name", + "2022-01-01", + "1.0.0", + "Woodlawn", + "123 Address", + "001 | md", + "TRUE", + ]); + expect(result).toHaveLength(0); + }); + + it("should return no errors when the value of last_updated_on is formatted as MM/DD/YYYY", () => { + const result = validator.validateHeaderRow([ + "name", + "01/07/2024", + "1.0.0", + "Woodlawn", + "123 Address", + "001 | MD", + "true", + ]); + expect(result).toHaveLength(0); + }); + + it("should return no errors when the value of last_updated_on is formatted as M/D/YYYY", () => { + const result = validator.validateHeaderRow([ + "name", + "1/7/2024", + "1.0.0", + "Woodlawn", + "123 Address", + "001 | MD", + "true", + ]); + expect(result).toHaveLength(0); + }); + + it("should return a RequiredValueError error for any header row value that is empty", () => { + const result = validator.validateHeaderRow([ + "", + "", + "", + "", + "", + "", + "", + ]); + // expected length is 6 since license number is optional + expect(result).toHaveLength(6); + expect(result.every((csvErr) => csvErr instanceof RequiredValueError)); + const requiredValueHeaderColumns = [ + "hospital_name", + "last_updated_on", + "version", + "hospital_location", + "hospital_address", + AFFIRMATION, + ]; + requiredValueHeaderColumns.forEach((headerColumn) => { + expect(result).toContainEqual( + expect.objectContaining({ + columnName: headerColumn, + }) + ); + }); + }); + + it("should return an InvalidDateError when the last_updated_on value is not a valid date", () => { + const result = validator.validateHeaderRow([ + "name", + "2022-14-01", + "1.0.0", + "Woodlawn", + "123 Address", + "001 | MD", + "true", + ]); + expect(result).toHaveLength(1); + expect(result[0]).toEqual( + new InvalidDateError(1, 1, "last_updated_on", "2022-14-01") + ); + }); + + it("should return an AllowedValuesError when the affirmation value is not true or false", () => { + const result = validator.validateHeaderRow([ + "name", + "2022-01-01", + "1.0.0", + "Woodlawn", + "123 Address", + "001 | MD", + "yes", + ]); + expect(result).toHaveLength(1); + expect(result[0]).toEqual( + new AllowedValuesError(1, 6, AFFIRMATION, "yes", ["true", "false"]) + ); + }); + }); + + describe("#validateColumns", () => { + it("should return no errors when valid tall columns are provided", () => { + // order of the columns does not matter + const columns = shuffle([ + "description", + "setting", + "drug_unit_of_measurement", + "drug_type_of_measurement", + "modifiers", + "standard_charge | gross", + "standard_charge | discounted_cash", + "standard_charge | min", + "standard_charge | max", + "additional_generic_notes", + "payer_name", + "plan_name", + "standard_charge | negotiated_dollar", + "standard_charge | negotiated_percentage", + "standard_charge | negotiated_algorithm", + "standard_charge | methodology", + "estimated_amount", + ]); + const result = validator.validateColumns(columns); + expect(result).toHaveLength(0); + expect(validator.isTall).toBe(true); + expect(validator.dataColumns).toEqual(columns); + }); + + it("should return no errors when valid wide columns are provided", () => { + // order of the columns does not matter + const columns = shuffle([ + "description", + "setting", + "drug_unit_of_measurement", + "drug_type_of_measurement", + "modifiers", + "standard_charge | gross", + "standard_charge | discounted_cash", + "standard_charge | min", + "standard_charge | max", + "standard_charge | Payer ABC | Plan 1 | negotiated_dollar", + "standard_charge | Payer ABC | Plan 1 | negotiated_percentage", + "standard_charge | Payer ABC | Plan 1 | negotiated_algorithm", + "standard_charge | Payer ABC | Plan 1 | methodology", + "estimated_amount | Payer ABC | Plan 1", + "additional_payer_notes | Payer ABC | Plan 1", + "additional_generic_notes", + ]); + const result = validator.validateColumns(columns); + expect(result).toHaveLength(0); + expect(validator.isTall).toBe(false); + expect(validator.dataColumns).toEqual(columns); + }); + + it.todo( + "should return an error when both tall and wide columns are provided" + ); + + it.todo( + "should return an error when neither tall nor wide columns are provided" + ); + + it.todo( + "should return an error when a code column is present without a corresponding code type column" + ); + + it.todo( + "should return an error when a code type column is present without a corresponding code column" + ); + + it.todo( + "should return errors when some payer-plan specific columns are missing" + ); + }); + + describe("#validateDataRow tall", () => { + it.todo("should return no errors when a valid tall data row is provided"); + + it.todo("should return an error when no description is provided"); + + it.todo("should return an error when no setting is provided"); + + it.todo( + "should return an error when setting is not one of the allowed values" + ); + + it.todo( + "should return an error when drug unit of measurement is present, but not a positive number" + ); + + it.todo( + "should return an error when drug unit of measurement is missing and drug type of measurement is present" + ); + + it.todo( + "should return an error when drug type of measurement is not one of the allowed values" + ); + + it.todo( + "should return an error when drug type of measurement is missing and drug unit of measurement is present" + ); + + it.todo( + "should return an error when gross standard charge is present, but not a positive number" + ); + + it.todo( + "should return an error when discounted cash standard charge is present, but not a positive number" + ); + + it.todo( + "should return an error when minimum standard charge is present, but not a positive number" + ); + + it.todo( + "should return an error when maximum standard charge is present, but not a positive number" + ); + + it.todo("should return an error when no code pairs are present"); + + it.todo( + "should return no errors when the first code pair is empty, but another code pair is present" + ); + + it.todo( + "should return an error when a code is present without a code type" + ); + + it.todo( + "should return an error when a code type is present without a code" + ); + + it.todo( + "should return an error when a code type is not one of the allowed values" + ); + + // If a "payer specific negotiated charge" is encoded as a dollar amount, percentage, or algorithm + // then a corresponding valid value for the payer name, plan name, and standard charge methodology must also be encoded. + it.todo( + "should return no errors when a payer specific negotiated charge is a dollar amount and valid values exist for payer name, plan name, and methodology" + ); + + it.todo( + "should return errors when a payer specific negotiated charge is a dollar amount, but no valid values exist for payer name, plan name, or methodology" + ); + + it.todo( + "should return no errors when a payer specific negotiated charge is a percentage and valid values exist for payer name, plan name, and methodology" + ); + + it.todo( + "should return errors when a payer specific negotiated charge is a percentage, but no valid values exist for payer name, plan name, or methodology" + ); + + it.todo( + "should return no errors when a payer specific negotiated charge is an algorithm and valid values exist for payer name, plan name, and methodology" + ); + + it.todo( + "should return errors when a payer specific negotiated charge is an algorithm, but no valid values exist for payer name, plan name, or methodology" + ); + + // If the "standard charge methodology" encoded value is "other", there must be a corresponding explanation found + // in the "additional notes" for the associated payer-specific negotiated charge. + it.todo( + "should return no errors when methodology is 'other' and additional notes are present" + ); + + it.todo( + "should return an error when methodology is 'other' and additional notes are missing" + ); + + // If an item or service is encoded, a corresponding valid value must be encoded for at least one of the following: + // "Gross Charge", "Discounted Cash Price", "Payer-Specific Negotiated Charge: Dollar Amount", "Payer-Specific Negotiated Charge: Percentage", + // "Payer-Specific Negotiated Charge: Algorithm". + it.todo( + "should return an error when an item or service is encoded with no charges" + ); + + it.todo( + "should return no errors when an item or service is encoded with a gross charge" + ); + + it.todo( + "should return no errors when an item or service is encoded with a discounted cash price" + ); + + it.todo( + "should return no errors when an item or service is encoded with a payer-specific dollar amount" + ); + + it.todo( + "should return no errors when an item or service is encoded with a payer-specific percentage" + ); + + it.todo( + "should return no errors when an item or service is encoded with a payer-specific algorithm" + ); + + // If there is a "payer specific negotiated charge" encoded as a dollar amount, + // there must be a corresponding valid value encoded for the deidentified minimum and deidentified maximum negotiated charge data. + it.todo( + "should return an error when a payer-specific dollar amount is encoded without a min or max" + ); + + it.todo( + "should return no errors when a payer-specific percentage is encoded without a min or max" + ); + + it.todo( + "should return no errors when a payer-specific algorithm is encoded without a min or max" + ); + + // If a modifier is encoded without an item or service, then a description and one of the following + // is the minimum information required: + // additional_generic_notes, standard_charge | negotiated_dollar, standard_charge | negotiated_percentage, or standard_charge | negotiated_algorithm + it.todo( + "should return an error when a modifier is encoded without an item or service, but none of the informational fields are present" + ); + + it.todo( + "should return no errors when a modifier is encoded without an item or service and additional notes are provided" + ); + + it.todo( + "should return no errors when a modifier is encoded without an item or service and a payer specific dollar amount is provided" + ); + + it.todo( + "should return no errors when a modifier is encoded without an item or service and a payer specific percentage is provided" + ); + + it.todo( + "should return no errors when a modifier is encoded without an item or service and a payer specific algorithm is provided" + ); + + // If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, + // then a corresponding "Estimated Allowed Amount" must also be encoded. new in v2.2.0 + it.todo( + "should return no errors when a payer-specific percentage is encoded with an estimated allowed amount" + ); + + it.todo( + "should return an error when a payer-specific percentage is encoded without an estimated allowed amount" + ); + + it.todo( + "should return no errors when a payer-specific algorithm is encoded with an estimated allowed amount" + ); + + it.todo( + "should return an error when a payer-specific algorithm is encoded without an estimated allowed amount" + ); + + // If code type is NDC, then the corresponding drug unit of measure and + // drug type of measure data elements must be encoded. new in v2.2.0 + it.todo( + "should return an error when code type is NDC, but no drug information is present" + ); + + it.todo( + "should return an error when more than one code is present, a code other than the first is NDC, but no drug information is present" + ); + }); + + describe("#validateDataRow wide", () => { + it.todo("should return no errors when a valid wide data row is provided"); + + // If a "payer specific negotiated charge" is encoded as a dollar amount, percentage, or algorithm + // then a corresponding valid value for the payer name, plan name, and standard charge methodology must also be encoded. + // Since the wide format incorporates payer name and plan name into the column name, only methodology is checked. + it.todo( + "should return no errors when a payer specific negotiated charge is a dollar amount and a valid value exists for methodology" + ); + + it.todo( + "should return errors when a payer specific negotiated charge is a dollar amount, but no valid value exists for methodology" + ); + + it.todo( + "should return no errors when a payer specific negotiated charge is a percentage and a valid value exists for methodology" + ); + + it.todo( + "should return errors when a payer specific negotiated charge is a percentage, but no valid value exists for methodology" + ); + + it.todo( + "should return no errors when a payer specific negotiated charge is an algorithm and a valid value exists for methodology" + ); + + it.todo( + "should return errors when a payer specific negotiated charge is an algorithm, but no valid value exists for methodology" + ); + + // If the "standard charge methodology" encoded value is "other", there must be a corresponding explanation found + // in the "additional notes" for the associated payer-specific negotiated charge. + it.todo( + "should return no errors when a methodology is 'other' and payer-specific notes are provided" + ); + + it.todo( + "should return an error when a methodology is 'other' and payer-specific notes are not provided" + ); + + it.todo( + "should return an error when a methodology is 'other' and payer-specific notes are provided for a different payer and plan" + ); + + // If an item or service is encoded, a corresponding valid value must be encoded for at least one of the following: + // "Gross Charge", "Discounted Cash Price", "Payer-Specific Negotiated Charge: Dollar Amount", "Payer-Specific Negotiated Charge: Percentage", + // "Payer-Specific Negotiated Charge: Algorithm". + it.todo( + "should return an error when an item or service is encoded without any charges" + ); + + it.todo( + "should return no errors when an item or service with only a gross charge" + ); + + it.todo( + "should return no errors when an item or service with only a discounted cash price" + ); + + it.todo( + "should return no errors when an item or service with only a payer-specific dollar amount" + ); + + it.todo( + "should return no errors when an item or service with only a payer-specific percentage" + ); + + it.todo( + "should return no errors when an item or service with only a payer-specific algorithm" + ); + + // If there is a "payer specific negotiated charge" encoded as a dollar amount, + // there must be a corresponding valid value encoded for the deidentified minimum and deidentified maximum negotiated charge data. + it.todo( + "should return an error when a payer-specific dollar amount is encoded without a minimum or maximum" + ); + + it.todo( + "should return no errors when a payer-specific percentage is encoded without a minimum or maximum" + ); + + it.todo( + "should return no errors when a payer-specific algorithm is encoded without a minimum or maximum" + ); + + // If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, + // then a corresponding "Estimated Allowed Amount" must also be encoded. new in v2.2.0 + it.todo( + "should return no errors when a payer-specific percentage and an estimated allowed amount are provided" + ); + + it.todo( + "should return an error when a payer-specific percentage but no estimated allowed amount are provided" + ); + + it.todo( + "should return no errors when a payer-specific algorithm and an estimated allowed amount are provided" + ); + + it.todo( + "should return an error when a payer-specific algorithm but no estimated allowed amount are provided" + ); + + it.todo( + "should return an error when a payer-specific percentage is provided, but the estimated allowed amount is provided for a different payer and plan" + ); + + it.todo( + "should return an error when a payer-specific algorithm is provided, but the estimated allowed amount is provided for a different payer and plan" + ); + + // If code type is NDC, then the corresponding drug unit of measure and + // drug type of measure data elements must be encoded. new in v2.2.0 + it.todo( + "should return an error when code type is NDC, but no drug information is present" + ); + + it.todo( + "should return an error when more than one code is present, a code other than the first is NDC, but no drug information is present" + ); + }); + }); +}); diff --git a/test/validators/JsonValidator.test.ts b/test/validators/JsonValidator.test.ts new file mode 100644 index 0000000..ff7edb7 --- /dev/null +++ b/test/validators/JsonValidator.test.ts @@ -0,0 +1,179 @@ +import { InvalidJsonError } from "../../src/errors/json/InvalidJsonError"; +import { JsonValidator } from "../../src/validators/JsonValidator"; +import { createFixtureStream } from "../testhelpers/createFixtureStream"; + +describe("JsonValidator", () => { + describe("schema v2.0.0", () => { + // this is the earliest version of the schema supported by the validator. + let validator: JsonValidator; + + beforeAll(() => { + validator = new JsonValidator("v2.0.0"); + }); + + it("should validate a valid file", async () => { + const input = createFixtureStream("sample-valid.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it("should validate a file that starts with a byte-order mark", async () => { + const input = createFixtureStream("sample-valid-bom.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }, 10000); + + it("should validate an empty json file", async () => { + const input = createFixtureStream("sample-empty.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(false); + expect(result.errors).toHaveLength(8); + }); + + it("should validate a syntactically invalid JSON file", async () => { + const input = createFixtureStream("sample-invalid.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(false); + expect(result.errors).toHaveLength(1); + expect(result.errors[0]).toBeInstanceOf(InvalidJsonError); + }); + + it("should limit the number of errors returned when the maxErrors option is used", async () => { + const input = createFixtureStream("sample-empty.json"); + const result = await validator.validate(input, { maxErrors: 2 }); + expect(result.valid).toBe(false); + expect(result.errors).toHaveLength(2); + }); + }); + + describe("schema v2.1.0", () => { + // this version adds several conditional requirements: + // 1. If the "standard charge methodology" encoded value is "other", there must be a corresponding + // explanation found in the "additional notes" for the associated payer-specific negotiated charge. + // 2. If an item or service is encoded, a corresponding valid value must be encoded for at least one + // of the following: "Gross Charge", "Discounted Cash Price", "Payer-Specific Negotiated Charge: Dollar Amount", + // "Payer-Specific Negotiated Charge: Percentage", "Payer-Specific Negotiated Charge: Algorithm". + // 3. If there is a "payer specific negotiated charge" encoded as a dollar amount, there must be a corresponding + // valid value encoded for the deidentified minimum and deidentified maximum negotiated charge data. + let validator: JsonValidator; + + beforeAll(() => { + validator = new JsonValidator("v2.1.0"); + }); + + it("should validate a valid file", async () => { + const input = createFixtureStream("sample-valid.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it("should validate a file that starts with a byte-order mark", async () => { + const input = createFixtureStream("sample-valid-bom.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }, 10000); + + it("should validate an empty json file", async () => { + const input = createFixtureStream("sample-empty.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(false); + expect(result.errors).toHaveLength(8); + }); + + it("should validate a syntactically invalid JSON file", async () => { + const input = createFixtureStream("sample-invalid.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(false); + expect(result.errors).toHaveLength(1); + expect(result.errors[0]).toBeInstanceOf(InvalidJsonError); + }); + + it("should limit the number of errors returned when the maxErrors option is used", async () => { + const input = createFixtureStream("sample-empty.json"); + const result = await validator.validate(input, { maxErrors: 2 }); + expect(result.valid).toBe(false); + expect(result.errors).toHaveLength(2); + }); + + it.todo( + "should validate a file where a methodology is given as 'other', but no additional notes are provided" + ); + + it.todo( + "should validate a file where a standard charge object has none of the possible charges" + ); + + it.todo( + "should validate a file where there is a payer-specific dollar amount, but minimum and maximum are missing" + ); + }); + + describe("schema v2.2.0", () => { + // this version adds drug information and modifier information to the schema. + // there are two new conditional checks: + // 4. If a "payer specific negotiated charge" can only be expressed as a percentage or algorithm, then + // a corresponding "Estimated Allowed Amount" must also be encoded. + // 5. If code type is NDC, then the corresponding drug unit of measure and drug type of measure data elements must be encoded. + let validator: JsonValidator; + + beforeAll(() => { + validator = new JsonValidator("v2.2.0"); + }); + + it("should validate a valid file", async () => { + const input = createFixtureStream("sample-valid.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + it("should validate a file that starts with a byte-order mark", async () => { + const input = createFixtureStream("sample-valid-bom.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }, 10000); + + it("should validate an empty json file", async () => { + const input = createFixtureStream("sample-empty.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(false); + expect(result.errors).toHaveLength(8); + }); + + it("should validate a syntactically invalid JSON file", async () => { + const input = createFixtureStream("sample-invalid.json"); + const result = await validator.validate(input); + expect(result.valid).toBe(false); + expect(result.errors).toHaveLength(1); + expect(result.errors[0]).toBeInstanceOf(InvalidJsonError); + }); + + it("should limit the number of errors returned when the maxErrors option is used", async () => { + const input = createFixtureStream("sample-empty.json"); + const result = await validator.validate(input, { maxErrors: 2 }); + expect(result.valid).toBe(false); + expect(result.errors).toHaveLength(2); + }); + + it.todo("should validate a file that uses drug information correctly"); + + it.todo("should validate a file with incorrectly formed drug information"); + + it.todo("should validate a file that uses modifier information correctly"); + + it.todo( + "should validate a file with incorrectly formed modifier information" + ); + + it.todo( + "should validate a file where a charge is expressed as a percentage or algorithm, but no estimated allowed amount is provided" + ); + + it.todo("should validate a file with an NDC code but no drug information"); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 03ae8fd..c657ce8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,6 @@ "esModuleInterop": true, "declaration": true, "outDir": "./lib", - "rootDir": "./src" + "baseUrl": "./" } }