diff --git a/CHANGELOG.md b/CHANGELOG.md index 859f645..06a21a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic ### Added +- Phase e original HIPAA baselines for 837P/I, 835, 270/271, 276/277 and 834; exact SNIP 3 balancing, occurrence-scoped SNIP 4 rules, SNIP 5 formats and a host `ExternalCodeSetProvider` SPI. +- Test-only healthcare maps, classified sample tables, seeded synthetic fixtures, Cedar Health Clearinghouse demo traffic and a PHI canary scanner covering both ingress paths, 999s, apply/replay/resend, widgets, query exports, alerts and logs. +- Permission-checked, session-bound Download Raw Document actions for inbox/interchange rows, with a value-free user audit event. + - Phase d warehouse and logistics baseline guides for 940, 945, 214 and 204 in 004010 and 005010 (syntax notes, situational control-total and stop rules, AT7 pairing), inbound/outbound sample maps (940 in, 945 out/in, 214 in, 204 in/out), DATEPART/TIMEPART outbound transforms and `+`-joined inbound paths for date/time pairs, and Harbor Logistics and Ridgeline Freight demo traffic. -||||||| parent of bfb63ce (feat: phase c - AS2 transport) - Phase c AS2: `As2Transport` send and receive (RFC 4130) with S/MIME signing (SHA-256/384/512; SHA1 flagged), encryption (AES-128/256 CBC or GCM; 3DES flagged), optional compression, synchronous and asynchronous signed MDNs with MIC verification, Test Connection, optional TLS pinning and VAN transports with no partner. Built on as2-lib 5.1.8 and BouncyCastle, entirely in memory (no keystore or temporary files). - `POST /qqq/hooks/edi/as2/{transport}` and `/{transport}/mdn` route provider: identity, decryption (ACTIVE or NEXT keys), signature and MIC checks, Message-ID dedupe returning the original MDN, error MDNs with no inbox row, and a queued, retried asynchronous MDN sender. @@ -40,4 +43,8 @@ Following [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and [Semantic ### Fixed +- 999 now accepts SNIP 3+ findings with `E` instead of rejecting every ERROR. HIPAA acknowledgement selection follows the implementation version and enabled agreement expectation; outbound HIPAA ST carries its implementation identifier. +- Qualified HL/NM1 anchors distinguish healthcare hierarchy levels; repeated loop names under different parents retain separate occurrences. Situational rules inherit direct ancestor segments without borrowing values from sibling occurrences. +- Review/Result records expose identifiers only. A marked RC.5 PHI logging bridge prevents core upsert query diagnostics from copying claim/member values into logs. + - Inbound apply no longer fails with a null-pointer error on a target table that has no associations (flat tables such as the 214 status sample). diff --git a/README.md b/README.md index 71163b9..6ba91bf 100644 --- a/README.md +++ b/README.md @@ -256,6 +256,52 @@ the agreement document `ackDueMinutes` (60 when unset). Existing databases need the new `edi_as2_mdn` table, `edi_outbound_message.transport_mic` and `edi_transport.weak_crypto` (see `docs/ddl/edi-postgres.sql`). +## HIPAA and PHI + +Original **QQQ baseline guides** cover 837P (005010X222A1), 837I (005010X223A2), +835 (005010X221A1), 270/271 (005010X279A1), 276/277 (005010X212), 834 (005010X220A1) +and 999 (005010X231A1). These are in-house structure and usage baselines, not licensed TR3s. +No TR3 text or full licensed code lists ship. Hosts may import their licensed guides. + +SNIP 1–2 covers envelopes and the active guide. SNIP 3 uses exact decimals for claim charges, +claim/line CAS adjustments and payments less signed PLB adjustments (zero tolerance). SNIP 4 +rules run per loop occurrence, including dependent patients, claim/line dates, diagnoses, +subscriber demographics and benefits-or-rejection responses. SNIP 3+ findings produce `E` and +an exception; SNIP 1–2 errors produce `R`. HIPAA replies use 999 when acknowledgement is enabled. +SNIP 5 includes ICD-10 diagnosis shape, NPI Luhn with the 80840 prefix, and US ZIP shape. +`ExternalCodeSetProvider.isValid(codeSet, code, asOf)` supplies licensed membership checks for +EXTERNAL code lists; without it, only formats are checked. The System widget reports that state. + +Healthcare agreement documents default to PHI and SNIP 4. Clinical keys go only to +`edi_transaction.phiBusinessKey`; validation excerpts, acknowledgements, events, exceptions, +alerts and process summaries contain no submitted clinical values. Healthcare sample maps and +host tables live **only in test sources/resources** and use seeded synthetic patients and claims. +Their NPI `1234567893` is a checksum test fixture, not an asserted provider identity. + +**The host must encrypt the database at rest and should install qbit-secured-data.** Payloads +are always AES-GCM encrypted; PHI columns rely on host database encryption. RC.5's +`EdiClassifications` stand-in marks PHI/PII/CONFIDENTIAL and denies PHI AI exposure, but does not +mask values. Without secured-data policies, a user with table access can read PHI columns. +The home explicitly reports this limitation. Use record-level audits on host PHI target tables +until core classified-value audit protection is available. + +RC.5 also logs query criteria verbatim. The marked `PhiLogScope` bridge suppresses synchronous +logger diagnostics during PHI mapping/application and preview; ordinary value-free EDI events +remain persisted. It changes no logger levels and does not suppress other threads. Replace both +bridges with core secured-data support at 4.1.0-RC.6. Map samples use record-level audits to avoid +copying expected-result PHI into audit details. Host code outside these scopes must protect its +own logging, audits and asynchronous work. + +Download Raw Document uses a session-bound process grant, rechecks `edi.raw.download` and record +read/partner access at download time, streams decrypted bytes without local files, and records +`RAW_DOWNLOADED` with the user and row references, never content. Download filenames are generic. + +`PhiCanaryTest` scans every five-character marker window through gateway/HTTPS intake, forced +SNIP 2/3/4 errors, mapping/apply, 999, replay/resend, Review/Result, alerts, DEBUG logs, all EDI +query exports and all home/child widgets. FULL exports exempt only PHI-classified columns; +RC.5 HIDDEN field-lock exports have no exemptions. MASKED tiers and the optional Quick Search +qbit require the newer secured-data integration and are not claimed by these RC.5 tests. + ## Build and verification ```bash diff --git a/docs/DEMO.md b/docs/DEMO.md index 16d1f55..40e292f 100644 --- a/docs/DEMO.md +++ b/docs/DEMO.md @@ -44,7 +44,8 @@ The in-memory gateway accepts every send, and the rejected documents produce rej The SFTP/HTTPS partner settings are illustrative `.invalid` destinations. Their agreements have no outbound transport, so they cannot contact a real partner; resulting acknowledgement send -retries are visible. Northwind alone has configured outbound documents. The in-memory gateway +retries are visible. Northwind has the retail outbound documents; the logistics and healthcare +scenarios add their own outbound configurations. The in-memory gateway is restored after the intentional dead-letter scenario. Standard Send As EDI, Test Connection, Resend, Resend As New and Cancel actions are available. The gateway stores bytes in memory; it does not expose a separate partner download UI. @@ -58,3 +59,17 @@ descending creation date and ID. The home exception queue puts OPEN before IN_PR first within each status. RC.5 has no table-level default-sort metadata to override Next's sort. Inbound message labels use external IDs because filenames are classified as PHI. Light/dark visual layout and production scheduler/gateway deployment remain host acceptance. + +Phase e adds a fifth partner, **Cedar Health Clearinghouse** (CEDAR, PHI enabled), after the existing +retail and logistics scenarios. It sends seeded synthetic professional and institutional claims, +a remittance, a professional claim with a one-dollar balancing error (SNIP 3), and a professional +claim missing its dependent patient loop (SNIP 4). Both business-rule errors are accepted with +errors (`E`) and open exceptions. Value-free 999s go to the in-memory clearinghouse outbox. +A synthetic eligibility request goes out as a 270 and a matching-trace 271 arrives. Clinical keys +appear only in the classified PHI key column, not the ordinary document key or record labels. + +The healthcare sample maps and tables are test-only. Patients are seeded `Testpatient Alpha` +fixtures, member IDs begin with `SYN`, and `1234567893` is a documented NPI checksum test value. +The host database must be encrypted for real PHI; this disposable demo uses synthetic data only. +The System widget shows format-only external code validation until a provider is registered. +Inbox and interchange records offer the audited **Download Raw Document** action. diff --git a/docs/DESIGN.md b/docs/DESIGN.md index aed3a51..ccff243 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -750,6 +750,13 @@ Non-HIPAA documents validate at level 2 by default and HIPAA documents at level document can raise or lower its level. ### 11.4 External code sets + +JSON element constraints support `source: "EXTERNAL"` and `externalCodeSet`. Normalized code-list +rows preserve both properties. Register the provider with +`EdiQBitConfig.withExternalCodeSetProvider(...)`; it is excluded from configuration JSON. +Membership failures are positional SNIP 5 findings. The effective date uses a service date, +then a payment date, otherwise the configured clock. Provider failures disclose no values. + `ExternalCodeSetProvider` (SPI: `boolean isValid(String codeSet, String code, LocalDate asOf)`), registered by the host, for example backed by a licensed table the host loads. Code lists with `source = EXTERNAL` delegate to it. Without a provider they check the format only, and the home says @@ -778,13 +785,17 @@ so. - **Minimum necessary:** PHI maps write only the mapped fields. The raw document stays encrypted and can be downloaded only with the record action "Download Raw Document" (permission `edi.raw.download`), which is audited and, with secured-data, written to its access log. -- **Canary egress tests** (phase e, the core SECURED-DATA procedure): synthetic 837P and 835 - documents carry `CANARY-PHI-` in the patient name, member id, claim number and a - diagnosis. They run through receive, validate (with forced errors), 999, map, apply, exception, - reprocess, resend, the home widgets, exports of every `edi_` table, Quick Search, alerts and - captured logs, as a FULL, a MASKED and a HIDDEN user. The test fails on any substring of the canary - longer than 4 characters outside the encrypted payloads and the PHI-classified columns, and for the - MASKED and HIDDEN users anywhere at all. +- **Canary egress tests** (phase e, the core SECURED-DATA procedure): two randomized markers per + patient-name, member-id, claim-number and diagnosis field run through gateway and HTTPS hooks, + validation with SNIP 2/3/4 failures, 999, map/apply, exceptions, reprocess/resend, every home and + child widget, every persisted `edi_` table's QueryAction export, Review/Result, captured alerts + and an in-memory DEBUG log appender. Every five-character window is scanned, detecting all + longer substrings. Only explicit PHI value/display-value columns are exempt for FULL exports; + ciphertext bytes are checked for plaintext. RC.5 HIDDEN field locks are separately exercised + without exemptions. The storage-only download alias has no query surface. MASKED tiers and + the optional Quick Search index await their host/core integration; they are not simulated. + Long diagnosis markers use a widened test-only guide element so accepted-with-error fixtures + can also reach mapping/apply; separate malformed monetary elements force structural rejection. - No real PHI anywhere in the repo: samples are synthetic, produced by a seeded generator. ## 13. App home and navigation @@ -955,7 +966,7 @@ RC.5 adaptations implemented in this worktree: - `InboundApplier` assumed every target table has associations; a flat table (the 214 status sample) hit a null pointer. It is now null-safe. - The 214 sample map records the first LX event per transaction into a flat status table and always inserts (no match key), so repeated statuses form a history; a host that wants every LX event maps an association. - Demo: Harbor Logistics gets a gateway OUTBOX transport for 945s (the partner pulls over SFTP, per 5.3) after the retail seed has run, so its earlier acknowledgements keep their visible retries; the fourth partner Ridgeline Freight is added, so `EdiDemoServerTest` now expects four partners. -||||||| parent of bfb63ce (feat: phase c - AS2 transport) + ## Phase c implementation notes (2026-10-14) @@ -977,3 +988,29 @@ RC.5 adaptations implemented in this worktree: `lastError` instead of a generic text. - Not implemented: EC and RSA-PSS signing keys (RSA only), MIC comparison under a different algorithm than the one requested, CAQH/Peppol profiles, and AS2 over a host-mounted client certificate (`MTLS`). + + +## Phase e implementation notes (2026-10-14) + +- Core RC.5 logs full query criteria, including PHI upsert keys. `PhiLogScope` is a scoped, + marked bridge to suppress synchronous diagnostic logging during PHI map/apply and previews; + it leaves unrelated threads and persisted value-free events intact. Remove with the core + classification/logging bridge at RC.6. Map samples use record-level audits on RC.5 because their + expected-result column can contain PHI. Host asynchronous code, target audits and queries outside + these boundaries still need host secured-data protection. Raw downloads use RC.5's existing + session-bound storage grant and an `edi_event` audit rather than a not-yet-available access log. +- Situational rules bind to their occurrence and inherit only direct ancestor segments, so a + sibling line's service date cannot satisfy another line. Fixed loop-head qualifiers distinguish + HL/NM1 roles; the same 2300/2400 names can occur under subscriber and patient parents. +- The 271 baseline requires benefits or an AAA rejection for a traced response; it does not + perform cross-document 270/271 correlation. Provider differences that cannot be inferred from + the submitted document belong in a companion guide or host rule; no rendering provider is + invented from an absent identifier. +- Healthcare maps and sample target tables are test-only, per the phase-e scope. The 837 sample + map is a single-claim example with service-line associations; the validator supports multiple + claims and checks each independently. The 835 and 834 samples map multiple claim/member + associations. These are baseline examples, not a complete licensed TR3 implementation. +- Child-list widgets explicitly enforce their nested queries on RC.5; the generic renderer + otherwise bypasses field locks inside the already-authorized widget action scope. +- MASKED-user response scanning and Quick Search indexing are not available on the pinned RC.5 + test classpath. FULL and actual HIDDEN-field-lock scans run now; no simulated masking is used. diff --git a/src/main/java/com/kingsrook/qbits/edi/EdiQBitConfig.java b/src/main/java/com/kingsrook/qbits/edi/EdiQBitConfig.java index b61d403..2fc423a 100644 --- a/src/main/java/com/kingsrook/qbits/edi/EdiQBitConfig.java +++ b/src/main/java/com/kingsrook/qbits/edi/EdiQBitConfig.java @@ -55,6 +55,26 @@ public class EdiQBitConfig implements QBitConfig return (java.util.Set.copyOf(sendAsEdiTables)); } + private com.kingsrook.qbits.edi.guide.ExternalCodeSetProvider externalCodeSetProvider; + + /******************************************************************************* + ** Optional host provider for licensed external code sets. + *******************************************************************************/ + @JsonIgnore + public com.kingsrook.qbits.edi.guide.ExternalCodeSetProvider getExternalCodeSetProvider() + { + return (externalCodeSetProvider); + } + + /******************************************************************************* + ** Register a provider; null retains format-only validation. + *******************************************************************************/ + public EdiQBitConfig withExternalCodeSetProvider(com.kingsrook.qbits.edi.guide.ExternalCodeSetProvider value) + { + externalCodeSetProvider = value; + return (this); + } + private String payloadBucketAlias = "edi"; private java.util.function.Supplier serviceSessionSupplier = null; private java.time.Clock clock = java.time.Clock.systemUTC(); diff --git a/src/main/java/com/kingsrook/qbits/edi/EdiRuntime.java b/src/main/java/com/kingsrook/qbits/edi/EdiRuntime.java index 49761c2..75abd84 100644 --- a/src/main/java/com/kingsrook/qbits/edi/EdiRuntime.java +++ b/src/main/java/com/kingsrook/qbits/edi/EdiRuntime.java @@ -53,7 +53,7 @@ public final class EdiRuntime private static final Map INSTANCES = Collections.synchronizedMap(new WeakHashMap<>()); private final X12EnvelopeReader reader; private final X12Writer writer = new X12Writer(); - private final GuideValidator validator = new GuideValidator(); + private final GuideValidator validator; private final BuiltInGuideLoader guides = new BuiltInGuideLoader(); private final com.kingsrook.qbits.edi.storage.PayloadStore payloads; private final EdiQBitConfig config; @@ -71,6 +71,7 @@ public final class EdiRuntime private EdiRuntime(QInstance instance, EdiQBitConfig config) { this.config = config; + validator = new GuideValidator(config); reader = new X12EnvelopeReader(config.getMaxSegmentsPerTransaction()); payloads = "OBJECT_STORAGE".equals(config.getPayloadStore()) ? new com.kingsrook.qbits.edi.storage.ObjectStoragePayloadStore(new PayloadCrypto(config.getSecretsResolver()), config.getPayloadBucketAlias(), config.getClock()) diff --git a/src/main/java/com/kingsrook/qbits/edi/ack/FunctionalAckBuilder.java b/src/main/java/com/kingsrook/qbits/edi/ack/FunctionalAckBuilder.java index ec6b5a3..b2ec0ad 100644 --- a/src/main/java/com/kingsrook/qbits/edi/ack/FunctionalAckBuilder.java +++ b/src/main/java/com/kingsrook/qbits/edi/ack/FunctionalAckBuilder.java @@ -57,7 +57,7 @@ final class FunctionalAckBuilder for(X12TransactionSet transaction : group.transactions()) { List errors = findings.getOrDefault(transaction.st().value(2), List.of()); - boolean rejected = !transaction.findings().isEmpty() || errors.stream().anyMatch(f -> "ERROR".equals(f.severity())); + boolean rejected = !transaction.findings().isEmpty() || errors.stream().anyMatch(f -> "ERROR".equals(f.severity()) && f.snipLevel() <= 2); String status = rejected ? "R" : errors.isEmpty() ? "A" : "E"; if(!rejected) { diff --git a/src/main/java/com/kingsrook/qbits/edi/app/EdiChildRecordListRenderer.java b/src/main/java/com/kingsrook/qbits/edi/app/EdiChildRecordListRenderer.java new file mode 100644 index 0000000..53f88a4 --- /dev/null +++ b/src/main/java/com/kingsrook/qbits/edi/app/EdiChildRecordListRenderer.java @@ -0,0 +1,47 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.kingsrook.qbits.edi.app; + + +import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ChildRecordListRenderer; +import com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput; + + +/******************************************************************************* + ** Explicitly enforce table and field permissions inside the widget's action scope. + ** RC.5 treats nested DEFAULT queries as trusted, so the generic renderer alone + ** does not remove fields denied by a FieldSecurityLock. + *******************************************************************************/ +public class EdiChildRecordListRenderer extends ChildRecordListRenderer +{ + /******************************************************************************* + ** Use the standard typed child list with an enforced query at its public boundary. + *******************************************************************************/ + @Override + protected QueryOutput executeQuery(QueryInput input) throws QException + { + input.setPermissionEnforcement(PermissionEnforcement.ENFORCE); + return (super.executeQuery(input)); + } +} diff --git a/src/main/java/com/kingsrook/qbits/edi/app/EdiHome.java b/src/main/java/com/kingsrook/qbits/edi/app/EdiHome.java index 0e88141..43cb853 100644 --- a/src/main/java/com/kingsrook/qbits/edi/app/EdiHome.java +++ b/src/main/java/com/kingsrook/qbits/edi/app/EdiHome.java @@ -312,7 +312,7 @@ public class EdiHome extends AbstractWidgetRenderer List> rows = new ArrayList<>(); rows.add(row("Item", "Payload storage", "Status", "DATABASE".equals(store) ? "Database, encrypted (demos and tests)" : "Object storage, encrypted")); rows.add(row("Item", "Secured data", "Status", secured ? "Installed" : "Not installed: PHI fields are not masked")); - rows.add(row("Item", "External code sets", "Status", "Not configured")); + rows.add(row("Item", "External code sets", "Status", EdiRuntime.current().config().getExternalCodeSetProvider() == null ? "Format only: no provider configured" : "Provider configured")); rows.add(row("Item", "Inbound backlog", "Status", countOf("edi_inbox", inboxBacklog()) + (oldestIn.isEmpty() ? "" : ", oldest " + humanize(Duration.between(oldestIn.getFirst().getValueInstant("receivedAt"), now))))); rows.add(row("Item", "Outbound queue", "Status", countOf("edi_outbound_message", outboundQueue()) diff --git a/src/main/java/com/kingsrook/qbits/edi/flow/InboundProcessor.java b/src/main/java/com/kingsrook/qbits/edi/flow/InboundProcessor.java index 8092fc2..60a6360 100644 --- a/src/main/java/com/kingsrook/qbits/edi/flow/InboundProcessor.java +++ b/src/main/java/com/kingsrook/qbits/edi/flow/InboundProcessor.java @@ -185,7 +185,7 @@ public class InboundProcessor if(EdiRecords.query("edi_outbound_message", EdiRecords.equal("ackForGroupId", groupRow.getValue("id"))).isEmpty()) { AckLevel level = AckLevel.valueOf(Objects.requireNonNullElse(config.getValueString("ackLevel"), "ERRORS_ONLY")); - boolean hipaa = Boolean.TRUE.equals(config.getValueBoolean("containsPhi")) && group.gs().value(8).startsWith("005010X"); + boolean hipaa = group.gs().value(8).startsWith("005010X") || "999".equals(config.getValueString("ackExpectation")); var ack = hipaa ? runtime.ack999().build(group, findings, level, "0001") : runtime.ack997().build(group, findings, level, "0001"); queue(AckEnvelope.wrap(x12, group, ack, control(agreement, "ISA"), control(agreement, "GS"), LocalDateTime.now(runtime.config().getClock())), agreement, id, groupRow.getValueInteger("id"), hipaa ? "999" : "997"); @@ -261,6 +261,7 @@ public class InboundProcessor .withValue("receiverQualifier", x12.isa().value(7)).withValue("receiverId", x12.isa().value(8).trim()).withValue("controlNumber", x12.isa().value(13)) .withValue("interchangeVersion", x12.isa().value(12)).withValue("usageIndicator", x12.isa().value(15)).withValue("groupCount", x12.groups().size()) .withValue("payloadRef", inbox.getValue("payloadRef")).withValue("payloadSha256", inbox.getValue("payloadSha256")).withValue("sizeBytes", inbox.getValue("sizeBytes")) + .withValue("containsPhi", x12.groups().stream().anyMatch(g -> g.gs().value(8).startsWith("005010X"))) .withValue("status", duplicate ? "DUPLICATE" : x12.findings().isEmpty() ? "ACCEPTED" : "REJECTED")); EdiJournal.event(inbox.getValueInteger("id"), row.getValueInteger("id"), null, "PARSED"); return (row); @@ -307,7 +308,7 @@ public class InboundProcessor return (SystemWrites.insert("edi_transaction", new QRecord().withValue("inboundPosition", position).withValue("interchangeId", interchange.getValue("id")).withValue("functionalGroupId", group.getValue("id")) .withValue("direction", "INBOUND").withValue("partnerId", interchange.getValue("partnerId")).withValue("transactionSet", tx.st().value(1)) .withValue("version", group.getValue("version")).withValue("controlNumber", tx.st().value(2)).withValue("segmentCount", tx.segments().size()) - .withValue("agreementDocumentId", document == null ? null : document.getValue("id")).withValue("containsPhi", document != null && Boolean.TRUE.equals(document.getValueBoolean("containsPhi"))) + .withValue("agreementDocumentId", document == null ? null : document.getValue("id")).withValue("containsPhi", group.getValueString("version").startsWith("005010X") || document != null && Boolean.TRUE.equals(document.getValueBoolean("containsPhi"))) .withValue("status", "RECEIVED"))); } @@ -352,7 +353,7 @@ public class InboundProcessor List findings = new ArrayList<>(); if(document != null && document.getValueInteger("guideId") != null) { - findings.addAll(runtime.validator().validate(tx, new GuideRepository().load(document.getValueInteger("guideId")), Boolean.TRUE.equals(document.getValueBoolean("containsPhi")))); + findings.addAll(runtime.validator().validate(tx, new GuideRepository().load(document.getValueInteger("guideId")), Boolean.TRUE.equals(document.getValueBoolean("containsPhi")) || group.gs().value(8).startsWith("005010X"))); int snip = Objects.requireNonNullElse(document.getValueInteger("snipLevel"), 2); findings.removeIf(f -> f.snipLevel() > snip); } @@ -432,7 +433,7 @@ public class InboundProcessor return; } String[] stage = {"MAPPING"}; - try + try(var logging = com.kingsrook.qbits.edi.security.PhiLogScope.open(Boolean.TRUE.equals(tx.getValueBoolean("containsPhi")))) { InboxWorker.fenced(inbox, lease, () -> { @@ -447,7 +448,7 @@ public class InboundProcessor stage[0] = "APPLY"; QRecord target = InboundApplier.asPartner(runtime.config().getServiceSessionSupplier(), agreement.getValueInteger("partnerId"), () -> new InboundApplier().apply(map, mapped.record())); String primaryKey = com.kingsrook.qqq.backend.core.context.QContext.getQInstance().getTable(map.getValueString("tableName")).getPrimaryKeyField(); - boolean phi = Boolean.TRUE.equals(document.getValueBoolean("containsPhi")); + boolean phi = Boolean.TRUE.equals(tx.getValueBoolean("containsPhi")); SystemWrites.update("edi_transaction", new QRecord().withValue("id", tx.getValue("id")).withValue("status", "APPLIED") .withValue("mapId", map.getValue("id")).withValue("mapRevision", map.getValue("revision")).withValue("businessKeyType", map.getValue("businessKeyType")) .withValue(phi ? "phiBusinessKey" : "businessKey", mapped.businessKey()).withValue("secondaryKey", phi ? null : mapped.secondaryKey()) diff --git a/src/main/java/com/kingsrook/qbits/edi/flow/OutboundWorker.java b/src/main/java/com/kingsrook/qbits/edi/flow/OutboundWorker.java index 190a036..cecd7ba 100644 --- a/src/main/java/com/kingsrook/qbits/edi/flow/OutboundWorker.java +++ b/src/main/java/com/kingsrook/qbits/edi/flow/OutboundWorker.java @@ -115,7 +115,7 @@ public class OutboundWorker { continue; } - try + try(var logging = com.kingsrook.qbits.edi.security.PhiLogScope.open(Boolean.TRUE.equals(document.getValueBoolean("containsPhi")))) { X12TransactionSet set = InboundApplier.asPartner(EdiRuntime.current().config().getServiceSessionSupplier(), agreement.getValueInteger("partnerId"), () -> { diff --git a/src/main/java/com/kingsrook/qbits/edi/guide/BuiltInGuideLoader.java b/src/main/java/com/kingsrook/qbits/edi/guide/BuiltInGuideLoader.java index 13e8b8c..56cbfff 100644 --- a/src/main/java/com/kingsrook/qbits/edi/guide/BuiltInGuideLoader.java +++ b/src/main/java/com/kingsrook/qbits/edi/guide/BuiltInGuideLoader.java @@ -37,7 +37,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord; *******************************************************************************/ public class BuiltInGuideLoader { - public static final List RESOURCES = List.of("850-004010", "850-005010", "997-004010", "997-005010", "999-005010X231A1", "855-004010", "855-005010", "810-004010", "810-005010", "846-004010", "846-005010", "860-004010", "860-005010", "856-004010", "856-005010", "940-004010", "940-005010", "945-004010", "945-005010", "214-004010", "214-005010", "204-004010", "204-005010"); + public static final List RESOURCES = List.of("850-004010", "850-005010", "997-004010", "997-005010", "999-005010X231A1", "855-004010", "855-005010", "810-004010", "810-005010", "846-004010", "846-005010", "860-004010", "860-005010", "856-004010", "856-005010", "940-004010", "940-005010", "945-004010", "945-005010", "214-004010", "214-005010", "204-004010", "204-005010", "837P-005010X222A1", "837I-005010X223A2", "835-005010X221A1", "270-005010X279A1", "271-005010X279A1", "276-005010X212", "277-005010X212", "834-005010X220A1"); /******************************************************************************* ** Read the documented JSON format in memory. diff --git a/src/main/java/com/kingsrook/qbits/edi/guide/ExternalCodeSetProvider.java b/src/main/java/com/kingsrook/qbits/edi/guide/ExternalCodeSetProvider.java new file mode 100644 index 0000000..59b423b --- /dev/null +++ b/src/main/java/com/kingsrook/qbits/edi/guide/ExternalCodeSetProvider.java @@ -0,0 +1,37 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.kingsrook.qbits.edi.guide; + + +import java.time.LocalDate; + + +/******************************************************************************* + ** Host-owned, licensed code sets. Implementations must not log submitted values. + *******************************************************************************/ +@FunctionalInterface +public interface ExternalCodeSetProvider +{ + /******************************************************************************* + ** Check membership on the document's effective service or payment date. + *******************************************************************************/ + boolean isValid(String codeSet, String code, LocalDate asOf); +} diff --git a/src/main/java/com/kingsrook/qbits/edi/guide/GuideElement.java b/src/main/java/com/kingsrook/qbits/edi/guide/GuideElement.java index ec2f3a3..292e5c7 100644 --- a/src/main/java/com/kingsrook/qbits/edi/guide/GuideElement.java +++ b/src/main/java/com/kingsrook/qbits/edi/guide/GuideElement.java @@ -27,6 +27,13 @@ import java.util.List; /******************************************************************************* ** An element or composite component constraint. *******************************************************************************/ -public record GuideElement(Integer position, Integer componentPosition, String elementId, String dataType, Integer minLength, Integer maxLength, String usage, List codes, String fixedValue, Integer repeatMax, String condition) +public record GuideElement(Integer position, Integer componentPosition, String elementId, String dataType, Integer minLength, Integer maxLength, String usage, List codes, String fixedValue, Integer repeatMax, String condition, String source, String externalCodeSet) { + /******************************************************************************* + ** Preserve the original constructor for host-authored inline constraints. + *******************************************************************************/ + public GuideElement(Integer position, Integer componentPosition, String elementId, String dataType, Integer minLength, Integer maxLength, String usage, List codes, String fixedValue, Integer repeatMax, String condition) + { + this(position, componentPosition, elementId, dataType, minLength, maxLength, usage, codes, fixedValue, repeatMax, condition, null, null); + } } diff --git a/src/main/java/com/kingsrook/qbits/edi/guide/GuideRepository.java b/src/main/java/com/kingsrook/qbits/edi/guide/GuideRepository.java index 76ce527..0e386ad 100644 --- a/src/main/java/com/kingsrook/qbits/edi/guide/GuideRepository.java +++ b/src/main/java/com/kingsrook/qbits/edi/guide/GuideRepository.java @@ -59,8 +59,10 @@ public class GuideRepository for(QRecord element : rows("edi_guide_element", "guideSegmentId", segmentId)) { List codes = null; + QRecord codeList = null; if(element.getValueInteger("codeListId") != null) { + codeList = rows("edi_code_list", "id", element.getValueInteger("codeListId")).getFirst(); codes = new ArrayList<>(); for(QRecord code : rows("edi_code_list_value", "codeListId", element.getValueInteger("codeListId"))) { @@ -72,7 +74,7 @@ public class GuideRepository } elements.add(new GuideElement(element.getValueInteger("position"), element.getValueInteger("componentPosition"), element.getValueString("elementId"), element.getValueString("dataType"), element.getValueInteger("minLength"), element.getValueInteger("maxLength"), element.getValueString("usage"), codes, - element.getValueString("fixedValue"), element.getValueInteger("repeatMax"), condition(rules, element.getValueInteger("situationalRuleId")))); + element.getValueString("fixedValue"), element.getValueInteger("repeatMax"), condition(rules, element.getValueInteger("situationalRuleId")), codeList == null ? null : codeList.getValueString("source"), codeList == null ? null : codeList.getValueString("externalCodeSet"))); } List segmentRules = rules.stream().filter(r -> segmentId.equals(r.getValueInteger("guideSegmentId")) && !"condition".equals(r.getValueString("message"))).map(GuideRepository::rule).toList(); segments.add(new GuideSegment(definition.getValueString("segmentId"), definition.getValueString("loopId"), definition.getValueString("parentLoopId"), @@ -107,11 +109,11 @@ public class GuideRepository for(GuideElement element : list(segment.elements())) { Integer codeListId = null; - if(element.codes() != null) + if(element.codes() != null || "EXTERNAL".equals(element.source())) { codeListId = insert("edi_code_list", new QRecord().withValue("name", guide.name() + "/" + revision + "/" + sequence + "/" + element.position()) - .withValue("elementId", element.elementId()).withValue("source", "BUILT_IN"), transaction).getValueInteger("id"); - for(String code : element.codes()) + .withValue("elementId", element.elementId()).withValue("source", element.source() == null ? "BUILT_IN" : element.source()).withValue("externalCodeSet", element.externalCodeSet()), transaction).getValueInteger("id"); + for(String code : list(element.codes())) { insert("edi_code_list_value", new QRecord().withValue("codeListId", codeListId).withValue("code", code).withValue("isActive", true), transaction); } diff --git a/src/main/java/com/kingsrook/qbits/edi/guide/GuideValidator.java b/src/main/java/com/kingsrook/qbits/edi/guide/GuideValidator.java index 6e82bdb..2a661c7 100644 --- a/src/main/java/com/kingsrook/qbits/edi/guide/GuideValidator.java +++ b/src/main/java/com/kingsrook/qbits/edi/guide/GuideValidator.java @@ -34,31 +34,31 @@ import com.kingsrook.qbits.edi.x12.X12TransactionSet; *******************************************************************************/ public class GuideValidator { + private final com.kingsrook.qbits.edi.EdiQBitConfig config; + + /******************************************************************************* + ** Standalone validation uses built-in formats without a licensed provider. + *******************************************************************************/ + public GuideValidator() + { + this(new com.kingsrook.qbits.edi.EdiQBitConfig()); + } + + /******************************************************************************* + ** Read the host provider at validation time, allowing runtime registration. + *******************************************************************************/ + public GuideValidator(com.kingsrook.qbits.edi.EdiQBitConfig config) + { + this.config = config; + } + /******************************************************************************* ** Validate one transaction; diagnostic text never contains submitted values. *******************************************************************************/ public List validate(X12TransactionSet transaction, Guide guide, boolean containsPhi) { State state = new State(transaction.segments(), containsPhi); - List roots = new ArrayList<>(); - Map nodes = new LinkedHashMap<>(); - for(GuideSegment segment : guide.segments()) - { - List destination = roots; - if(segment.loopId() != null && !segment.loopId().isBlank()) - { - Node loop = nodes.get(segment.loopId()); - if(loop == null) - { - loop = new Node(segment.loopId(), null); - nodes.put(segment.loopId(), loop); - Node parent = nodes.get(segment.parentLoopId()); - (parent == null ? roots : parent.children).add(loop); - } - destination = loop.children; - } - destination.add(new Node(null, segment)); - } + List roots = tree(guide); consume(roots, state, false); while(state.index < state.segments.size()) { @@ -93,11 +93,16 @@ public class GuideValidator GuideSegment definition = binding.definition(); X12Segment segment = binding.segment(); elements(segment, definition, evaluator, state); + external(segment, definition, state); for(GuideRule rule : list(definition.rules())) { - rule(rule, segment, definition.loopId(), evaluator, state); + rule(rule, segment, definition.loopId(), "SITUATIONAL".equals(rule.type()) && definition.loopId() != null ? scoped(binding, state) : evaluator, state); } } + if(guide.version().startsWith("005010X")) + { + state.findings.addAll(new HealthcareValidation().validate(transaction, guide, state.loops)); + } return (List.copyOf(state.findings)); } @@ -107,27 +112,64 @@ public class GuideValidator public Map>> loops(X12TransactionSet transaction, Guide guide) { State state = new State(transaction.segments(), true); + List roots = tree(guide); + consume(roots, state, false); + return (state.loops); + } + + /******************************************************************************* + ** The same loop name can occur under subscriber and dependent patient parents. + *******************************************************************************/ + private static List tree(Guide guide) + { List roots = new ArrayList<>(); - Map nodes = new LinkedHashMap<>(); + Map latest = new LinkedHashMap<>(); for(GuideSegment segment : guide.segments()) { List destination = roots; if(segment.loopId() != null && !segment.loopId().isBlank()) { - Node loop = nodes.get(segment.loopId()); + Node parent = latest.get(segment.parentLoopId()); + List siblings = parent == null ? roots : parent.children; + Node loop = siblings.stream().filter(n -> segment.loopId().equals(n.loopId)).findFirst().orElse(null); if(loop == null) { loop = new Node(segment.loopId(), null); - nodes.put(segment.loopId(), loop); - Node parent = nodes.get(segment.parentLoopId()); - (parent == null ? roots : parent.children).add(loop); + siblings.add(loop); } + latest.put(segment.loopId(), loop); destination = loop.children; } destination.add(new Node(null, segment)); } - consume(roots, state, false); - return (state.loops); + return (roots); + } + + /******************************************************************************* + ** Situational rules see their occurrence plus direct ancestor segments, never siblings. + *******************************************************************************/ + private static RuleEvaluator scoped(Binding binding, State state) + { + List scope = state.loops.getOrDefault(binding.definition().loopId(), List.of()).stream() + .filter(o -> o.contains(binding.segment())).min(java.util.Comparator.comparingInt(List::size)).orElse(state.segments); + List visible = new ArrayList<>(scope); + for(List ancestor : state.loops.values().stream().flatMap(List::stream).toList()) + { + if(ancestor.size() > scope.size() && ancestor.contains(scope.getFirst())) + { + for(X12Segment segment : ancestor) + { + boolean nested = state.loops.values().stream().flatMap(List::stream) + .anyMatch(o -> o.size() < ancestor.size() && ancestor.contains(o.getFirst()) && o.contains(segment)); + if(!nested && !visible.contains(segment)) + { + visible.add(segment); + } + } + } + } + visible.sort(java.util.Comparator.comparingInt(X12Segment::position)); + return (new RuleEvaluator(visible, state.loops)); } /******************************************************************************* @@ -141,7 +183,7 @@ public class GuideValidator { GuideSegment head = node.children.getFirst().definition; int count = 0; - while(state.matches(head.segmentId())) + while(state.matches(head)) { int start = state.index; consume(node.children, state, true); @@ -165,7 +207,7 @@ public class GuideValidator GuideSegment definition = node.definition; int count = 0; int maximum = number(definition.maxUse(), 1); - while(state.matches(definition.segmentId()) && (count == 0 || !loopOccurrence || node != nodes.getFirst())) + while(state.matches(definition.segmentId()) && (node != nodes.getFirst() || !loopOccurrence || state.matches(definition)) && (count == 0 || !loopOccurrence || node != nodes.getFirst())) { X12Segment segment = state.segments.get(state.index++); state.bindings.add(new Binding(segment, definition)); @@ -235,7 +277,7 @@ public class GuideValidator state.add(segment, definition.loopId(), element, "8", code, "Invalid element data type", value); } if(element.fixedValue() != null && !element.fixedValue().equals(value) - || "ID".equals(element.dataType()) && element.codes() != null && !element.codes().contains(value)) + || !"EXTERNAL".equals(element.source()) && "ID".equals(element.dataType()) && element.codes() != null && !element.codes().contains(value)) { state.add(segment, definition.loopId(), element, "8", "7", "Invalid code or fixed value", value); } @@ -243,6 +285,47 @@ public class GuideValidator } } + /******************************************************************************* + ** External membership is SNIP 5 and is optional; formats are always built in. + *******************************************************************************/ + private void external(X12Segment segment, GuideSegment definition, State state) + { + if(config.getExternalCodeSetProvider() == null) + { + return; + } + for(GuideElement element : list(definition.elements())) + { + if(!"EXTERNAL".equals(element.source())) + { + continue; + } + int repetitions = element.position() <= segment.elements().size() ? segment.elements().get(element.position() - 1).size() : 1; + for(int repetition = 1; repetition <= repetitions; repetition++) + { + String value = segment.value(element.position(), repetition, number(element.componentPosition(), 1)); + if(!value.isEmpty()) + { + boolean valid; + try(var logging = com.kingsrook.qbits.edi.security.PhiLogScope.open(state.phi)) + { + valid = config.getExternalCodeSetProvider().isValid(element.externalCodeSet(), value, + HealthcareValidation.asOf(state.segments, config.getClock())); + } + catch(RuntimeException e) + { + valid = false; + } + if(!valid) + { + state.findings.add(new ValidationFinding(segment.position(), segment.id(), definition.loopId(), element.position(), element.componentPosition(), + 5, "8", "7", "8", "7", "ERROR", "External code validation failed", null)); + } + } + } + } + } + /******************************************************************************* ** Validate X12 lexical data types without coercing bad values. *******************************************************************************/ @@ -392,6 +475,15 @@ public class GuideValidator return (index < segments.size() && segments.get(index).id().equals(id)); } + /******************************************************************************* + ** Fixed qualifiers distinguish adjacent HL and NM1 loop anchors. + *******************************************************************************/ + private boolean matches(GuideSegment definition) + { + return (matches(definition.segmentId()) && list(definition.elements()).stream().filter(e -> e.fixedValue() != null) + .allMatch(e -> e.fixedValue().equals(segments.get(index).value(e.position(), 1, number(e.componentPosition(), 1))))); + } + /******************************************************************************* ** Locate a missing segment relative to the buffered transaction. *******************************************************************************/ diff --git a/src/main/java/com/kingsrook/qbits/edi/guide/HealthcareValidation.java b/src/main/java/com/kingsrook/qbits/edi/guide/HealthcareValidation.java new file mode 100644 index 0000000..b797229 --- /dev/null +++ b/src/main/java/com/kingsrook/qbits/edi/guide/HealthcareValidation.java @@ -0,0 +1,250 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.kingsrook.qbits.edi.guide; + + +import java.math.BigDecimal; +import java.time.Clock; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import com.kingsrook.qbits.edi.x12.X12Segment; +import com.kingsrook.qbits.edi.x12.X12TransactionSet; + + +/******************************************************************************* + ** Original healthcare balancing and format checks, without licensed code content. + *******************************************************************************/ +public class HealthcareValidation +{ + /******************************************************************************* + ** Exact decimal arithmetic; every diagnostic contains only positions and codes. + *******************************************************************************/ + public List validate(X12TransactionSet transaction, Guide guide, Map>> loops) + { + List findings = new ArrayList<>(); + List segments = transaction.segments(); + if("837".equals(guide.transactionSet())) + { + for(List claim : blocks(segments, "CLM")) + { + X12Segment head = claim.getFirst(); + try + { + BigDecimal lines = BigDecimal.ZERO; + for(X12Segment line : claim) + { + if(line.id().equals("SV1") || line.id().equals("SV2")) + { + lines = lines.add(amount(line, line.id().equals("SV1") ? 2 : 3)); + } + } + if(amount(head, 2).compareTo(lines) != 0) + { + findings.add(finding(head, "2300", 2, null, 3, "Claim charges do not balance")); + } + } + catch(NumberFormatException e) + { + findings.add(finding(head, "2300", 2, null, 3, "Claim balance cannot be calculated")); + } + } + } + if("835".equals(guide.transactionSet())) + { + remittance(segments, findings); + } + for(X12Segment segment : segments) + { + String loop = loops.entrySet().stream().filter(e -> e.getValue().stream().anyMatch(o -> o.contains(segment))) + .min(java.util.Comparator.comparingInt(e -> e.getValue().stream().filter(o -> o.contains(segment)).mapToInt(List::size).min().orElse(Integer.MAX_VALUE))) + .map(Map.Entry::getKey).orElse(null); + if(segment.id().equals("NM1") && segment.value(8).equals("XX") && !validNpi(segment.value(9))) + { + findings.add(finding(segment, loop, 9, null, 5, "Invalid NPI format or check digit")); + } + if(segment.id().equals("N4") && !segment.value(3).isEmpty() && (segment.value(4).isEmpty() || segment.value(4).equals("US")) + && !segment.value(3).matches("[0-9]{5}([0-9]{4}|-[0-9]{4})?")) + { + findings.add(finding(segment, loop, 3, null, 5, "Invalid postal format")); + } + if(segment.id().equals("HI")) + { + for(int position = 1; position <= segment.elements().size(); position++) + { + for(int repetition = 1; repetition <= segment.elements().get(position - 1).size(); repetition++) + { + if(List.of("ABK", "ABF", "ABJ", "APR").contains(segment.value(position, repetition, 1)) + && !segment.value(position, repetition, 2).matches("[A-Z][0-9][A-Z0-9](?:[A-Z0-9]{0,4}|\\.[A-Z0-9]{1,4})")) + { + findings.add(finding(segment, loop, position, 2, 5, "Invalid ICD-10 diagnosis format")); + } + } + } + } + } + return (findings); + } + + /******************************************************************************* + ** Claim and line CAS monetary positions are 3, 6, 9, 12, 15 and 18. + *******************************************************************************/ + private static void remittance(List segments, List findings) + { + for(List claim : blocks(segments, "CLP")) + { + X12Segment head = claim.getFirst(); + try + { + BigDecimal adjustments = BigDecimal.ZERO; + for(X12Segment segment : claim) + { + if(segment.id().equals("CAS")) + { + for(int p = 3; p <= segment.elements().size(); p += 3) + { + adjustments = adjustments.add(amount(segment, p)); + } + } + } + if(amount(head, 3).subtract(amount(head, 4)).compareTo(adjustments) != 0) + { + findings.add(finding(head, "2100", 3, null, 3, "Claim adjustments do not balance")); + } + } + catch(NumberFormatException e) + { + findings.add(finding(head, "2100", 3, null, 3, "Claim balance cannot be calculated")); + } + } + for(X12Segment payment : segments.stream().filter(s -> s.id().equals("BPR")).toList()) + { + try + { + BigDecimal total = BigDecimal.ZERO; + for(X12Segment segment : segments) + { + if(segment.id().equals("CLP")) + { + total = total.add(amount(segment, 4)); + } + if(segment.id().equals("PLB")) + { + for(int p = 4; p <= segment.elements().size(); p += 2) + { + total = total.subtract(amount(segment, p)); + } + } + } + if(amount(payment, 2).compareTo(total) != 0) + { + findings.add(finding(payment, null, 2, null, 3, "Payment does not balance")); + } + } + catch(NumberFormatException e) + { + findings.add(finding(payment, null, 2, null, 3, "Payment balance cannot be calculated")); + } + } + } + + /******************************************************************************* + ** Isolate each claim so one claim cannot compensate for another's imbalance. + *******************************************************************************/ + private static List> blocks(List segments, String anchor) + { + List> blocks = new ArrayList<>(); + List current = null; + for(X12Segment segment : segments) + { + if(segment.id().equals(anchor)) + { + current = new ArrayList<>(); + blocks.add(current); + } + if(current != null) + { + current.add(segment); + } + } + return (blocks); + } + + /******************************************************************************* + ** Missing monetary elements fail balancing rather than silently becoming zero. + *******************************************************************************/ + private static BigDecimal amount(X12Segment segment, int position) + { + return (new BigDecimal(segment.value(position))); + } + + /******************************************************************************* + ** Position-only finding with a positional trigger for the implementation ack. + *******************************************************************************/ + private static ValidationFinding finding(X12Segment segment, String loop, Integer element, Integer component, int level, String message) + { + return (new ValidationFinding(segment.position(), segment.id(), loop, element, component, level, + "8", "7", "8", "7", "ERROR", message, null) + .withContext(level == 3 ? new AckContext(segment.id(), segment.position(), loop, element, component) : null)); + } + + /******************************************************************************* + ** NPI uses the Luhn checksum over the 80840 prefix and all ten digits. + *******************************************************************************/ + public static boolean validNpi(String value) + { + if(!value.matches("[12][0-9]{9}")) + { + return (false); + } + String digits = "80840" + value; + int sum = 0; + for(int i = digits.length() - 1, n = 0; i >= 0; i--, n++) + { + int digit = digits.charAt(i) - '0'; + if(n % 2 == 1) + { + digit *= 2; + } + sum += digit > 9 ? digit - 9 : digit; + } + return (sum % 10 == 0); + } + + /******************************************************************************* + ** Service date, then payment date, then the configured clock for undated documents. + *******************************************************************************/ + public static LocalDate asOf(List segments, Clock clock) + { + for(X12Segment segment : segments) + { + String date = segment.id().equals("DTP") && segment.value(1).equals("472") ? segment.value(3) + : segment.id().equals("BPR") ? segment.value(16) : ""; + if(RuleEvaluator.validDate(date) && date.length() == 8) + { + return (LocalDate.parse(date, DateTimeFormatter.BASIC_ISO_DATE)); + } + } + return (LocalDate.now(clock)); + } +} diff --git a/src/main/java/com/kingsrook/qbits/edi/map/MapPreview.java b/src/main/java/com/kingsrook/qbits/edi/map/MapPreview.java index fe29679..83c000c 100644 --- a/src/main/java/com/kingsrook/qbits/edi/map/MapPreview.java +++ b/src/main/java/com/kingsrook/qbits/edi/map/MapPreview.java @@ -51,7 +51,7 @@ public class MapPreview .withCriteria("containsPhi", com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator.EQUALS, true)).isEmpty()); var guide = new GuideRepository().load(map.getValueInteger("guideId")); ArrayList lines = new ArrayList<>(); - try(InputStream stream = runtime.payloads().open(new PayloadRef(sample.getValueString("payloadRef"), null, 0))) + try(var logging = com.kingsrook.qbits.edi.security.PhiLogScope.open(phi); InputStream stream = runtime.payloads().open(new PayloadRef(sample.getValueString("payloadRef"), null, 0))) { var interchanges = runtime.reader().read(stream); lines.add(new ProcessSummaryLine(Status.INFO, interchanges.size(), "interchanges parsed")); diff --git a/src/main/java/com/kingsrook/qbits/edi/map/OutboundMapper.java b/src/main/java/com/kingsrook/qbits/edi/map/OutboundMapper.java index d8824f5..dd5d027 100644 --- a/src/main/java/com/kingsrook/qbits/edi/map/OutboundMapper.java +++ b/src/main/java/com/kingsrook/qbits/edi/map/OutboundMapper.java @@ -62,7 +62,10 @@ public class OutboundMapper throw new IllegalArgumentException("An outbound map is required"); } List segments = new ArrayList<>(); - segments.add(X12Segment.of("ST", 1, map.getValueString("transactionSet"), control)); + String version = map.getValueString("version"); + segments.add(version != null && version.startsWith("005010X") + ? X12Segment.of("ST", 1, map.getValueString("transactionSet"), control, version) + : X12Segment.of("ST", 1, map.getValueString("transactionSet"), control)); List sorted = rules.stream().sorted(Comparator.comparing(r -> r.getValueInteger("sequence"))).toList(); emit(source, sorted, "", "", 1, "", new int[] {0}, segments); segments.add(X12Segment.of("SE", segments.size() + 1, String.valueOf(segments.size() + 1), control)); diff --git a/src/main/java/com/kingsrook/qbits/edi/metadata/EdiTables.java b/src/main/java/com/kingsrook/qbits/edi/metadata/EdiTables.java index 27327f9..abc1320 100644 --- a/src/main/java/com/kingsrook/qbits/edi/metadata/EdiTables.java +++ b/src/main/java/com/kingsrook/qbits/edi/metadata/EdiTables.java @@ -232,7 +232,7 @@ public final class EdiTables QTableMetaData table = new QTableMetaData().withName(name).withBackendName(config.getBackendName()) .withPrimaryKeyField("id").withFieldsFromEntity(entity).withRecordLabelFormatAndFields("%s", label) .withPermissionRules(new QPermissionRules().withPermissionBaseName("edi." + name.substring(4)).withLevel(PermissionLevel.READ_INSERT_EDIT_DELETE_PERMISSIONS)) - .withAuditRules(new QAuditRules().withAuditLevel(readOnly ? AuditLevel.RECORD : AuditLevel.FIELD)); + .withAuditRules(new QAuditRules().withAuditLevel(readOnly || name.equals("edi_map_sample") ? AuditLevel.RECORD : AuditLevel.FIELD)); QRecordEntity.registerTable(entity, table); if(List.of("edi_payload", "edi_certificate", "edi_inbox").contains(name)) { @@ -305,7 +305,7 @@ public final class EdiTables sources.add(enumSource("edi_interchange_status", "RECEIVED", "ACCEPTED", "ACCEPTED_WITH_ERRORS", "REJECTED", "DUPLICATE", "ENVELOPED", "SENT", "DELIVERED", "FAILED")); sources.add(enumSource("edi_ack_status", "A", "E", "P", "R", "NONE", "PENDING")); sources.add(enumSource("edi_transaction_status", "RECEIVED", "VALID", "INVALID", "MAPPED", "APPLIED", "EXCEPTION", "QUEUED", "SENT", "ACKNOWLEDGED", "REJECTED", "IGNORED")); - sources.add(enumSource("edi_event_type", "RECEIVED", "STORED", "PARSED", "VALIDATED", "ACK_SENT", "ACK_QUEUED", "MAPPED", "APPLIED", "ENVELOPED", "SENT", "MDN", "DELIVERED", "ACK_RECEIVED", "EXCEPTION", "REPROCESSED", "RESENT")); + sources.add(enumSource("edi_event_type", "RECEIVED", "STORED", "PARSED", "VALIDATED", "ACK_SENT", "ACK_QUEUED", "MAPPED", "APPLIED", "ENVELOPED", "SENT", "MDN", "DELIVERED", "ACK_RECEIVED", "EXCEPTION", "REPROCESSED", "RESENT", "RAW_DOWNLOADED")); sources.add(enumSource("edi_exception_type", "UNKNOWN_PARTNER", "PARTNER_MISMATCH", "NOT_CONFIGURED", "DUPLICATE", "ENVELOPE", "VALIDATION", "MAPPING", "APPLY", "TRANSPORT", "REJECTED_ACK", "OVERDUE_ACK", "DEAD_LETTER")); sources.add(enumSource("edi_exception_status", "OPEN", "IN_PROGRESS", "RESOLVED", "IGNORED")); sources.add(enumSource("edi_inbox_status", "PENDING", "PROCESSING", "PROCESSED", "RETRY_WAIT", "DEAD_LETTER", "DUPLICATE", "IGNORED")); @@ -422,7 +422,8 @@ public final class EdiTables { return (joins().stream().map(join -> ChildRecordListRenderer.widgetMetaDataBuilder(join).withName(join.getName()) .withLabel(joinLabel(join)).withCanAddChildRecord(false) - .withOrderBys(childOrder(join.getRightTable())).getWidgetMetaData().withGridColumns(12)).toList()); + .withOrderBys(childOrder(join.getRightTable())).getWidgetMetaData() + .withCodeReference(new QCodeReference(com.kingsrook.qbits.edi.app.EdiChildRecordListRenderer.class)).withGridColumns(12)).toList()); } /******************************************************************************* diff --git a/src/main/java/com/kingsrook/qbits/edi/processes/EdiProcesses.java b/src/main/java/com/kingsrook/qbits/edi/processes/EdiProcesses.java index e64c6b8..17616ac 100644 --- a/src/main/java/com/kingsrook/qbits/edi/processes/EdiProcesses.java +++ b/src/main/java/com/kingsrook/qbits/edi/processes/EdiProcesses.java @@ -75,6 +75,10 @@ public final class EdiProcesses { result.add(action(table, "preview", "Preview Map", "edi.map.preview")); } + for(String table : List.of("edi_inbox", "edi_interchange")) + { + result.add(action(table, "download", "Download Raw Document", "edi.raw.download").withMaxInputRecords(1)); + } result.add(action("edi_exception", "assign", "Assign", "edi.exception.assign")); result.add(action("edi_exception", "ignore", "Ignore", "edi.exception.ignore")); result.add(action("edi_exception", "resolve", "Mark Resolved", "edi.exception.resolve")); @@ -148,6 +152,16 @@ public final class EdiProcesses process.getFrontendStep("result").withViewField(new QFieldMetaData(field, QFieldType.STRING)); } } + if(operation.equals("download")) + { + process.getFrontendStep("result").withComponent(new com.kingsrook.qqq.backend.core.model.metadata.processes.QFrontendComponentMetaData() + .withType(com.kingsrook.qqq.backend.core.model.metadata.processes.QComponentType.DOWNLOAD_FORM)); + for(String field : List.of("downloadFileName", "storageTableName", "storageReference")) + { + process.getBackendStep("execute").getOutputMetaData().withField(new QFieldMetaData(field, QFieldType.STRING)); + process.getFrontendStep("result").withViewField(new QFieldMetaData(field, QFieldType.STRING)); + } + } if(operation.equals("assign")) { process.getFrontendStep("review").withFormField(new QFieldMetaData("assignedToUserId", QFieldType.STRING).withLabel("Assign to user ID").withIsRequired(true)); @@ -173,7 +187,7 @@ public final class EdiProcesses public void runOnePage(RunBackendStepInput input, RunBackendStepOutput output) { count += input.getRecords().size(); - output.getRecords().addAll(input.getRecords()); + input.getRecords().forEach(record -> output.getRecords().add(new QRecord().withValue("id", record.getValue("id")))); } /******************************************************************************* @@ -218,6 +232,20 @@ public final class EdiProcesses { lines.add(new ProcessSummaryLine(Status.OK, 1, "public certificate exported")); } + else if(operation.equals("download")) + { + if(!QContext.getQSession().hasPermission("edi.raw.download")) + { + throw new QException("Raw document download is not authorized"); + } + var storage = new com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput(com.kingsrook.qbits.edi.storage.PreviewDownloadBackend.TABLE) + .withReference("raw:" + table + ":" + row.getValueInteger("id")); + com.kingsrook.qqq.backend.core.actions.processes.ProcessFileDownload.registerStorage(storage); + output.addValue("downloadFileName", "edi-document.edi"); + output.addValue("storageTableName", storage.getTableName()); + output.addValue("storageReference", storage.getReference()); + lines.add(new ProcessSummaryLine(Status.OK, 1, "Document ready for authorized download")); + } else if(operation.equals("preview")) { QRecord map = table.equals("edi_map") ? row : EdiRecords.query("edi_map", EdiRecords.equal("id", row.getValue("mapId"))).getFirst(); @@ -316,7 +344,7 @@ public final class EdiProcesses output.addValue("storageTableName", storage.getTableName()); output.addValue("storageReference", ref.value()); } - output.getRecords().add(selected); + output.getRecords().add(new QRecord().withValue("id", selected.getValue("id"))); } catch(Exception e) { diff --git a/src/main/java/com/kingsrook/qbits/edi/security/PhiLogScope.java b/src/main/java/com/kingsrook/qbits/edi/security/PhiLogScope.java new file mode 100644 index 0000000..38e1274 --- /dev/null +++ b/src/main/java/com/kingsrook/qbits/edi/security/PhiLogScope.java @@ -0,0 +1,154 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.kingsrook.qbits.edi.security; + + +import java.util.Map; +import java.util.WeakHashMap; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.Logger; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.filter.AbstractFilter; +import org.apache.logging.log4j.message.Message; + + +/******************************************************************************* + ** STAND-IN(core=QRun-IO/qqq#1142, removeBy=4.1.0-RC.6). + ** RC.5 logs query criteria verbatim. Suppress synchronous diagnostic logging only + ** while PHI mapping/application runs; value-free EDI events remain in the database. + ** This does not replace host masking or protection of host code outside this scope. + *******************************************************************************/ +public final class PhiLogScope implements AutoCloseable +{ + private static final ThreadLocal DEPTH = ThreadLocal.withInitial(() -> 0); + private static final Map INSTALLED = new WeakHashMap<>(); + private final int previous; + + /******************************************************************************* + ** Protect nested PHI work without changing logger levels or unrelated threads. + *******************************************************************************/ + private PhiLogScope(boolean enabled) + { + previous = DEPTH.get(); + if(enabled) + { + install(); + DEPTH.set(previous + 1); + } + } + + /******************************************************************************* + ** Open a scope around code that may pass clinical values to RC.5 core actions. + *******************************************************************************/ + public static PhiLogScope open(boolean enabled) + { + return (new PhiLogScope(enabled)); + } + + /******************************************************************************* + ** Install once per logger configuration, including after a host reconfiguration. + *******************************************************************************/ + private static synchronized void install() + { + if(!(LogManager.getContext(false) instanceof LoggerContext context)) + { + throw new IllegalStateException("PHI processing requires the RC.5 Log4j protection bridge"); + } + Configuration configuration = context.getConfiguration(); + if(!INSTALLED.containsKey(configuration)) + { + ScopeFilter filter = new ScopeFilter(); + filter.start(); + configuration.addFilter(filter); + INSTALLED.put(configuration, true); + } + } + + /******************************************************************************* + ** Always restore nesting, including failed mapping or target transactions. + *******************************************************************************/ + @Override + public void close() + { + if(previous == 0) + { + DEPTH.remove(); + } + else + { + DEPTH.set(previous); + } + } + + /******************************************************************************* + ** Pre-event filter prevents sensitive diagnostics from reaching any appender. + *******************************************************************************/ + private static class ScopeFilter extends AbstractFilter + { + /******************************************************************************* + ** Decide before Log4j creates or formats a data-bearing event. + *******************************************************************************/ + private Result decision() + { + return (DEPTH.get() > 0 ? Result.DENY : Result.NEUTRAL); + } + + /******************************************************************************* + ** QLogger uses structured Log4j messages. + *******************************************************************************/ + @Override + public Result filter(Logger logger, Level level, Marker marker, Message message, Throwable error) + { + return (decision()); + } + + /******************************************************************************* + ** Object diagnostics from third-party mapping or target customizers. + *******************************************************************************/ + @Override + public Result filter(Logger logger, Level level, Marker marker, Object message, Throwable error) + { + return (decision()); + } + + /******************************************************************************* + ** Parameterized diagnostics from registered host extensions. + *******************************************************************************/ + @Override + public Result filter(Logger logger, Level level, Marker marker, String message, Object... parameters) + { + return (decision()); + } + + /******************************************************************************* + ** Synchronous event filtering for configurations invoking this filter later. + *******************************************************************************/ + @Override + public Result filter(LogEvent event) + { + return (decision()); + } + } +} diff --git a/src/main/java/com/kingsrook/qbits/edi/storage/PreviewDownloadBackend.java b/src/main/java/com/kingsrook/qbits/edi/storage/PreviewDownloadBackend.java index b8203a2..9bed82a 100644 --- a/src/main/java/com/kingsrook/qbits/edi/storage/PreviewDownloadBackend.java +++ b/src/main/java/com/kingsrook/qbits/edi/storage/PreviewDownloadBackend.java @@ -71,6 +71,10 @@ public class PreviewDownloadBackend implements QBackendModuleInterface, QStorage { throw new QException("Preview download is not authorized for this session"); } + if(input.getReference().startsWith("raw:")) + { + return (RawDocumentDownload.open(input.getReference())); + } return (SystemWrites.call(() -> EdiRuntime.current().payloads().open(new PayloadRef(input.getReference(), null, 0)))); } diff --git a/src/main/java/com/kingsrook/qbits/edi/storage/RawDocumentDownload.java b/src/main/java/com/kingsrook/qbits/edi/storage/RawDocumentDownload.java new file mode 100644 index 0000000..ece5059 --- /dev/null +++ b/src/main/java/com/kingsrook/qbits/edi/storage/RawDocumentDownload.java @@ -0,0 +1,79 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.kingsrook.qbits.edi.storage; + + +import java.io.InputStream; +import java.util.List; +import com.kingsrook.qbits.edi.EdiRuntime; +import com.kingsrook.qbits.edi.support.SystemWrites; +import com.kingsrook.qqq.backend.core.actions.permissions.PermissionEnforcement; +import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.exceptions.QException; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; + + +/******************************************************************************* + ** Stream an authorized archived document, with a value-free access event per download. + *******************************************************************************/ +public final class RawDocumentDownload +{ + /******************************************************************************* + ** Recheck permission and partner locks on every access, including registered URLs. + *******************************************************************************/ + public static InputStream open(String reference) throws QException + { + String[] parts = reference.split(":"); + if(parts.length != 3 || !parts[0].equals("raw") || !List.of("edi_inbox", "edi_interchange").contains(parts[1]) + || QContext.getQSession() == null || !QContext.getQSession().hasPermission("edi.raw.download")) + { + throw new QException("Raw document download is not authorized"); + } + Integer id; + try + { + id = Integer.valueOf(parts[2]); + } + catch(NumberFormatException e) + { + throw new QException("Invalid raw document reference"); + } + QueryInput query = new QueryInput(parts[1]).withFilter(EdiRecords.equal("id", id)); + query.setPermissionEnforcement(PermissionEnforcement.ENFORCE); + List rows = new QueryAction().execute(query).getRecords(); + if(rows.size() != 1 || rows.getFirst().getValueString("payloadRef") == null) + { + throw new QException("Raw document is unavailable"); + } + QRecord row = rows.getFirst(); + String user = QContext.getQSession().getUser() == null ? "system" : java.util.Objects.toString(QContext.getQSession().getUser().getIdReference(), "system"); + return (SystemWrites.call(() -> + { + SystemWrites.insert("edi_event", new QRecord().withValue("type", "RAW_DOWNLOADED").withValue("userName", user) + .withValue("inboxId", parts[1].equals("edi_inbox") ? id : row.getValueInteger("inboxId")) + .withValue("interchangeId", parts[1].equals("edi_interchange") ? id : null) + .withValue("at", EdiRuntime.current().config().getClock().instant()).withValue("detail", "Raw document download authorized")); + return (EdiRuntime.current().payloads().open(new PayloadRef(row.getValueString("payloadRef"), null, 0))); + })); + } +} diff --git a/src/main/resources/edi/guides/270-005010X279A1.json b/src/main/resources/edi/guides/270-005010X279A1.json new file mode 100644 index 0000000..de8c4db --- /dev/null +++ b/src/main/resources/edi/guides/270-005010X279A1.json @@ -0,0 +1,600 @@ +{ + "name": "QQQ baseline guide 270 005010X279A1", + "standard": "X12", + "version": "005010X279A1", + "transactionSet": "270", + "functionalIdCode": "HS", + "segments": [ + { + "segmentId": "ST", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "270" + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35, + "fixedValue": "005010X279A1" + } + ] + }, + { + "segmentId": "BHT", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 4 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + }, + { + "position": 4, + "dataType": "DT", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "20" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100A", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "PR" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000B", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "21" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100B", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "1P" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "22" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "TRN", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 2, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "IL" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "DMG", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "DT", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "EQ", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2110C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "codes": [ + "30" + ] + } + ] + }, + { + "segmentId": "SE", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 10 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + } + ] + } + ] +} diff --git a/src/main/resources/edi/guides/271-005010X279A1.json b/src/main/resources/edi/guides/271-005010X279A1.json new file mode 100644 index 0000000..83ff9fe --- /dev/null +++ b/src/main/resources/edi/guides/271-005010X279A1.json @@ -0,0 +1,699 @@ +{ + "name": "QQQ baseline guide 271 005010X279A1", + "standard": "X12", + "version": "005010X279A1", + "transactionSet": "271", + "functionalIdCode": "HB", + "segments": [ + { + "segmentId": "ST", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "271" + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35, + "fixedValue": "005010X279A1" + } + ] + }, + { + "segmentId": "BHT", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 4 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + }, + { + "position": 4, + "dataType": "DT", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "20" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100A", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "PR" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000B", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "21" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100B", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "1P" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "22" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "TRN", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 2, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "IL" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "DMG", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "DT", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "AAA", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999 + }, + { + "segmentId": "EB", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2110C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 7, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + } + ] + }, + { + "segmentId": "HSD", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2110C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999 + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2110C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999 + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2110C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "AAA", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2110C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999 + }, + { + "segmentId": "SE", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 10 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + } + ] + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(present(TRN02), or(present(EB), present(AAA)))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Benefits or rejection reason required for inquiry response" + } + ] +} diff --git a/src/main/resources/edi/guides/276-005010X212.json b/src/main/resources/edi/guides/276-005010X212.json new file mode 100644 index 0000000..0f89cd5 --- /dev/null +++ b/src/main/resources/edi/guides/276-005010X212.json @@ -0,0 +1,762 @@ +{ + "name": "QQQ baseline guide 276 005010X212", + "standard": "X12", + "version": "005010X212", + "transactionSet": "276", + "functionalIdCode": "HR", + "segments": [ + { + "segmentId": "ST", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "276" + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35, + "fixedValue": "005010X212" + } + ] + }, + { + "segmentId": "BHT", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 4 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + }, + { + "position": 4, + "dataType": "DT", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "20" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100A", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "PR" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000B", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "21" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100B", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "1P" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "22" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "TRN", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 2, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "IL" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "DMG", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "DT", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "TRN", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2200C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + } + ] + }, + { + "segmentId": "STC", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2200C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 5, + "componentPosition": 2 + }, + { + "position": 2, + "dataType": "DT", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2200C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999 + }, + { + "segmentId": "AMT", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2200C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999 + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2200C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "SVC", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2220C", + "parentLoopId": "2200C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2, + "componentPosition": 1 + }, + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "componentPosition": 2 + }, + { + "position": 2, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2220C", + "parentLoopId": "2200C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "SE", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 10 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + } + ] + } + ] +} diff --git a/src/main/resources/edi/guides/277-005010X212.json b/src/main/resources/edi/guides/277-005010X212.json new file mode 100644 index 0000000..54ac329 --- /dev/null +++ b/src/main/resources/edi/guides/277-005010X212.json @@ -0,0 +1,762 @@ +{ + "name": "QQQ baseline guide 277 005010X212", + "standard": "X12", + "version": "005010X212", + "transactionSet": "277", + "functionalIdCode": "HN", + "segments": [ + { + "segmentId": "ST", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "277" + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35, + "fixedValue": "005010X212" + } + ] + }, + { + "segmentId": "BHT", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 4 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + }, + { + "position": 4, + "dataType": "DT", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "20" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100A", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "PR" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000B", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "21" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100B", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "1P" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "22" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "TRN", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 2, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "IL" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "DMG", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "DT", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2100C", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "TRN", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2200C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + } + ] + }, + { + "segmentId": "STC", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2200C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 5, + "componentPosition": 2 + }, + { + "position": 2, + "dataType": "DT", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2200C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999 + }, + { + "segmentId": "AMT", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2200C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999 + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2200C", + "parentLoopId": "2100C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "SVC", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2220C", + "parentLoopId": "2200C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2, + "componentPosition": 1 + }, + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "componentPosition": 2 + }, + { + "position": 2, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2220C", + "parentLoopId": "2200C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "SE", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 10 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + } + ] + } + ] +} diff --git a/src/main/resources/edi/guides/834-005010X220A1.json b/src/main/resources/edi/guides/834-005010X220A1.json new file mode 100644 index 0000000..6423b20 --- /dev/null +++ b/src/main/resources/edi/guides/834-005010X220A1.json @@ -0,0 +1,539 @@ +{ + "name": "QQQ baseline guide 834 005010X220A1", + "standard": "X12", + "version": "005010X220A1", + "transactionSet": "834", + "functionalIdCode": "BE", + "segments": [ + { + "segmentId": "ST", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "834" + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35, + "fixedValue": "005010X220A1" + } + ] + }, + { + "segmentId": "BGN", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + }, + { + "position": 3, + "dataType": "DT", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 4, + "dataType": "TM", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5 + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "N1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "P5" + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + } + ] + }, + { + "segmentId": "N1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000B", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "IN" + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + } + ] + }, + { + "segmentId": "INS", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "Y", + "N" + ] + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(eq(INS01, 'Y'), present(DMG))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Subscriber demographics required", + "ik3Code": "8", + "ik4Code": "2" + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 10, + "loopId": "2000", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2000", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100A", + "parentLoopId": "2000", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "IL" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100A", + "parentLoopId": "2000", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100A", + "parentLoopId": "2000", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2100A", + "parentLoopId": "2000", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "DMG", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100A", + "parentLoopId": "2000", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "DT", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1 + } + ] + }, + { + "segmentId": "HD", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2300", + "parentLoopId": "2000", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2300", + "parentLoopId": "2000", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2300", + "parentLoopId": "2000", + "loopMaxRepeat": 999 + }, + { + "segmentId": "SE", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 10 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + } + ] + } + ] +} diff --git a/src/main/resources/edi/guides/835-005010X221A1.json b/src/main/resources/edi/guides/835-005010X221A1.json new file mode 100644 index 0000000..636a43a --- /dev/null +++ b/src/main/resources/edi/guides/835-005010X221A1.json @@ -0,0 +1,951 @@ +{ + "name": "QQQ baseline guide 835 005010X221A1", + "standard": "X12", + "version": "005010X221A1", + "transactionSet": "835", + "functionalIdCode": "HP", + "segments": [ + { + "segmentId": "ST", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "835" + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35, + "fixedValue": "005010X221A1" + } + ] + }, + { + "segmentId": "BPR", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1 + }, + { + "position": 2, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1 + }, + { + "position": 4, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 16, + "dataType": "DT", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8 + } + ] + }, + { + "segmentId": "TRN", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5 + }, + { + "segmentId": "DTM", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5 + }, + { + "segmentId": "N1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "PR" + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "1000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000B", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "PE" + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000B", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000B", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "1000B", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "LX", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 6 + } + ] + }, + { + "segmentId": "CLP", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2100", + "parentLoopId": "2000", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 38 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 4, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 5, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + } + ] + }, + { + "segmentId": "CAS", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 99, + "loopId": "2100", + "parentLoopId": "2000", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 3, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 4, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 5, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 6, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 7, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 9, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 10, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 11, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 12, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 13, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 14, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 15, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 16, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 17, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 18, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 19, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + } + ] + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 10, + "loopId": "2100", + "parentLoopId": "2000", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "MIA", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100", + "parentLoopId": "2000", + "loopMaxRepeat": 999 + }, + { + "segmentId": "MOA", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2100", + "parentLoopId": "2000", + "loopMaxRepeat": 999 + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 10, + "loopId": "2100", + "parentLoopId": "2000", + "loopMaxRepeat": 999 + }, + { + "segmentId": "DTM", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 10, + "loopId": "2100", + "parentLoopId": "2000", + "loopMaxRepeat": 999 + }, + { + "segmentId": "AMT", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 10, + "loopId": "2100", + "parentLoopId": "2000", + "loopMaxRepeat": 999 + }, + { + "segmentId": "SVC", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2110", + "parentLoopId": "2100", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "componentPosition": 1 + }, + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 5, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "HCPCS" + }, + { + "position": 2, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 3, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + } + ] + }, + { + "segmentId": "DTM", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2110", + "parentLoopId": "2100", + "loopMaxRepeat": 999 + }, + { + "segmentId": "CAS", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 99, + "loopId": "2110", + "parentLoopId": "2100", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 3, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 4, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 5, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 6, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 7, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 9, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 10, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 11, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 12, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 13, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 14, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 15, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 16, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 17, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "source": "EXTERNAL", + "externalCodeSet": "CARC" + }, + { + "position": 18, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 19, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 10, + "loopId": "2110", + "parentLoopId": "2100", + "loopMaxRepeat": 999 + }, + { + "segmentId": "AMT", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2110", + "parentLoopId": "2100", + "loopMaxRepeat": 999 + }, + { + "segmentId": "LQ", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2110", + "parentLoopId": "2100", + "loopMaxRepeat": 999 + }, + { + "segmentId": "PLB", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 99, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + }, + { + "position": 2, + "dataType": "DT", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 4, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 6, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 8, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 10, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 12, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 14, + "dataType": "R", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 18 + } + ] + }, + { + "segmentId": "SE", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 10 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + } + ] + } + ] +} diff --git a/src/main/resources/edi/guides/837I-005010X223A2.json b/src/main/resources/edi/guides/837I-005010X223A2.json new file mode 100644 index 0000000..55a0bff --- /dev/null +++ b/src/main/resources/edi/guides/837I-005010X223A2.json @@ -0,0 +1,2465 @@ +{ + "name": "QQQ baseline guide 837I 005010X223A2", + "standard": "X12", + "version": "005010X223A2", + "transactionSet": "837", + "functionalIdCode": "HC", + "segments": [ + { + "segmentId": "ST", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "837" + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35, + "fixedValue": "005010X223A2" + } + ] + }, + { + "segmentId": "BHT", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 4 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + }, + { + "position": 4, + "dataType": "DT", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 5, + "dataType": "TM", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 6, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + } + ] + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "41" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "PER", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 2, + "loopId": "1000A", + "parentLoopId": null, + "loopMaxRepeat": 999 + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000B", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "40" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "20" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "PRV", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2000A", + "parentLoopId": null, + "loopMaxRepeat": 999 + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2010AA", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "85" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010AA", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010AA", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2010AA", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000B", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "22" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "SBR", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000B", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(not(eq(SBR02, '18')), present(2000C))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Dependent patient loop required", + "ik3Code": "8", + "ik4Code": "2" + }, + { + "type": "SITUATIONAL", + "expression": "when(eq(SBR02, '18'), absent(2000C))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Subscriber has no dependent patient loop", + "ik3Code": "8", + "ik4Code": "2" + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2010BA", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "IL" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "DMG", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010BA", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "DT", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010BA", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010BA", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2010BA", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2010BB", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "PR" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010BB", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010BB", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2010BB", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "CLM", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2300", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 38 + }, + { + "position": 2, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "componentPosition": 1 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "componentPosition": 2 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "componentPosition": 3 + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(not(eq(CLM02, 0)), present(HI))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Diagnosis required for charged claim", + "ik3Code": "8", + "ik4Code": "2" + }, + { + "type": "SITUATIONAL", + "expression": "present(DTP[DTP01=434])", + "snipLevel": 4, + "severity": "ERROR", + "message": "Statement period required", + "ik3Code": "8", + "ik4Code": "2" + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2300", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 10, + "loopId": "2300", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HI", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 4, + "loopId": "2300", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 5, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 6, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 7, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 9, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 10, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 11, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 12, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 5, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 6, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 7, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 9, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 10, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 11, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 12, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + } + ] + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "82" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "LX", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 6 + } + ] + }, + { + "segmentId": "SV2", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 4 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2, + "componentPosition": 1 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "HCPCS" + }, + { + "position": 3, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 4, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 5, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 15 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2420A", + "parentLoopId": "2400", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "82" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "23" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(eq(HL03, '23'), present(PAT))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Patient relationship required" + } + ] + }, + { + "segmentId": "PAT", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2010CA", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "QC" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "DMG", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010CA", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "DT", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010CA", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010CA", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2010CA", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "CLM", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2300", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 38 + }, + { + "position": 2, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "componentPosition": 1 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "componentPosition": 2 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "componentPosition": 3 + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(not(eq(CLM02, 0)), present(HI))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Diagnosis required for charged claim", + "ik3Code": "8", + "ik4Code": "2" + }, + { + "type": "SITUATIONAL", + "expression": "present(DTP[DTP01=434])", + "snipLevel": 4, + "severity": "ERROR", + "message": "Statement period required", + "ik3Code": "8", + "ik4Code": "2" + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2300", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 10, + "loopId": "2300", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HI", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 4, + "loopId": "2300", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 5, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 6, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 7, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 9, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 10, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 11, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 12, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 5, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 6, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 7, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 9, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 10, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 11, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 12, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + } + ] + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "82" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "LX", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 6 + } + ] + }, + { + "segmentId": "SV2", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 4 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2, + "componentPosition": 1 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 5, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "HCPCS" + }, + { + "position": 3, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 4, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 5, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 15 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2420A", + "parentLoopId": "2400", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "82" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "SE", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 10 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + } + ] + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "present(CLM)", + "snipLevel": 2, + "severity": "ERROR", + "message": "Claim required" + } + ] +} diff --git a/src/main/resources/edi/guides/837P-005010X222A1.json b/src/main/resources/edi/guides/837P-005010X222A1.json new file mode 100644 index 0000000..97bcbd0 --- /dev/null +++ b/src/main/resources/edi/guides/837P-005010X222A1.json @@ -0,0 +1,2455 @@ +{ + "name": "QQQ baseline guide 837P 005010X222A1", + "standard": "X12", + "version": "005010X222A1", + "transactionSet": "837", + "functionalIdCode": "HC", + "segments": [ + { + "segmentId": "ST", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "837" + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35, + "fixedValue": "005010X222A1" + } + ] + }, + { + "segmentId": "BHT", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 4 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 50 + }, + { + "position": 4, + "dataType": "DT", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 5, + "dataType": "TM", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 6, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + } + ] + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "41" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "PER", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 2, + "loopId": "1000A", + "parentLoopId": null, + "loopMaxRepeat": 999 + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "1000B", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "40" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000A", + "parentLoopId": null, + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "20" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "PRV", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2000A", + "parentLoopId": null, + "loopMaxRepeat": 999 + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2010AA", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "85" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010AA", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010AA", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2010AA", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000B", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "22" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ] + }, + { + "segmentId": "SBR", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2000B", + "parentLoopId": "2000A", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(not(eq(SBR02, '18')), present(2000C))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Dependent patient loop required", + "ik3Code": "8", + "ik4Code": "2" + }, + { + "type": "SITUATIONAL", + "expression": "when(eq(SBR02, '18'), absent(2000C))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Subscriber has no dependent patient loop", + "ik3Code": "8", + "ik4Code": "2" + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2010BA", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "IL" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "DMG", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010BA", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "DT", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010BA", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010BA", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2010BA", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2010BB", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "PR" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010BB", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010BB", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2010BB", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "CLM", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2300", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 38 + }, + { + "position": 2, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "componentPosition": 1 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "componentPosition": 2 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "componentPosition": 3 + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(not(eq(CLM02, 0)), present(HI))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Diagnosis required for charged claim", + "ik3Code": "8", + "ik4Code": "2" + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2300", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 10, + "loopId": "2300", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HI", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 4, + "loopId": "2300", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 5, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 6, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 7, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 9, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 10, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 11, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 12, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 5, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 6, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 7, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 9, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 10, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 11, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 12, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + } + ] + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "82" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "LX", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 6 + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(present(SV102), present(DTP[DTP01=472]))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Service date required on line or claim", + "ik3Code": "8", + "ik4Code": "2" + } + ] + }, + { + "segmentId": "SV1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "componentPosition": 1 + }, + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 5, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "HCPCS" + }, + { + "position": 2, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 4, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 15 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2420A", + "parentLoopId": "2400", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "82" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HL", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 2, + "dataType": "N0", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 12 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "fixedValue": "23" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1, + "codes": [ + "0", + "1" + ] + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(eq(HL03, '23'), present(PAT))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Patient relationship required" + } + ] + }, + { + "segmentId": "PAT", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2000C", + "parentLoopId": "2000B", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + } + ] + }, + { + "segmentId": "NM1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2010CA", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "QC" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "DMG", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010CA", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "DT", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 1 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010CA", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2010CA", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2010CA", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "CLM", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2300", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 38 + }, + { + "position": 2, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "componentPosition": 1 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "componentPosition": 2 + }, + { + "position": 5, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "componentPosition": 3 + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(not(eq(CLM02, 0)), present(HI))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Diagnosis required for charged claim", + "ik3Code": "8", + "ik4Code": "2" + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2300", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 10, + "loopId": "2300", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "HI", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 4, + "loopId": "2300", + "parentLoopId": "2000C", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 5, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 6, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 7, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 9, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 10, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 11, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 12, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3, + "componentPosition": 1 + }, + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 5, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 6, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 7, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 9, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 10, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 11, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + }, + { + "position": 12, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 8, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "ICD10CM" + } + ] + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "82" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "N3", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 55 + } + ] + }, + { + "segmentId": "N4", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 30 + }, + { + "position": 2, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 3, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 15 + }, + { + "position": 4, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 3 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2310B", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "LX", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 6 + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "when(present(SV102), present(DTP[DTP01=472]))", + "snipLevel": 4, + "severity": "ERROR", + "message": "Service date required on line or claim", + "ik3Code": "8", + "ik4Code": "2" + } + ] + }, + { + "segmentId": "SV1", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2, + "componentPosition": 1 + }, + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 5, + "componentPosition": 2, + "source": "EXTERNAL", + "externalCodeSet": "HCPCS" + }, + { + "position": 2, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 18 + }, + { + "position": 3, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 4, + "dataType": "R", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 15 + } + ] + }, + { + "segmentId": "DTP", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 20, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "codes": [ + "D8", + "RD8" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 35 + } + ] + }, + { + "segmentId": "REF", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 5, + "loopId": "2400", + "parentLoopId": "2300", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3 + }, + { + "position": 2, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "NM1", + "usage": "SITUATIONAL", + "minUse": 0, + "maxUse": 1, + "loopId": "2420A", + "parentLoopId": "2400", + "loopMaxRepeat": 999, + "elements": [ + { + "position": 1, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 3, + "fixedValue": "82" + }, + { + "position": 2, + "dataType": "ID", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 1, + "codes": [ + "1", + "2" + ] + }, + { + "position": 3, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 60 + }, + { + "position": 4, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 35 + }, + { + "position": 8, + "dataType": "ID", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 2 + }, + { + "position": 9, + "dataType": "AN", + "usage": "SITUATIONAL", + "minLength": 1, + "maxLength": 80 + } + ] + }, + { + "segmentId": "SE", + "usage": "REQUIRED", + "minUse": 1, + "maxUse": 1, + "elements": [ + { + "position": 1, + "dataType": "N0", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 10 + }, + { + "position": 2, + "dataType": "AN", + "usage": "REQUIRED", + "minLength": 1, + "maxLength": 9 + } + ] + } + ], + "rules": [ + { + "type": "SITUATIONAL", + "expression": "present(CLM)", + "snipLevel": 2, + "severity": "ERROR", + "message": "Claim required" + } + ] +} diff --git a/src/main/resources/edi/guides/999-005010X231A1.json b/src/main/resources/edi/guides/999-005010X231A1.json index a40266a..29e8c5d 100644 --- a/src/main/resources/edi/guides/999-005010X231A1.json +++ b/src/main/resources/edi/guides/999-005010X231A1.json @@ -1,5 +1,5 @@ { - "name": "QQQ baseline 999 005010X231A1", + "name": "QQQ baseline guide 999 005010X231A1", "standard": "X12", "version": "005010X231A1", "transactionSet": "999", diff --git a/src/test/java/com/kingsrook/qbits/edi/HealthcareSupport.java b/src/test/java/com/kingsrook/qbits/edi/HealthcareSupport.java new file mode 100644 index 0000000..bbd02d3 --- /dev/null +++ b/src/test/java/com/kingsrook/qbits/edi/HealthcareSupport.java @@ -0,0 +1,239 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.kingsrook.qbits.edi; + + +import java.io.ByteArrayInputStream; +import java.math.BigDecimal; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import com.fasterxml.jackson.databind.JsonNode; +import com.kingsrook.qbits.edi.storage.EdiRecords; +import com.kingsrook.qbits.edi.support.SystemWrites; +import com.kingsrook.qbits.edi.x12.X12EnvelopeReader; +import com.kingsrook.qbits.edi.x12.X12Interchange; +import com.kingsrook.qbits.edi.x12.X12Segment; +import com.kingsrook.qbits.edi.x12.X12Separators; +import com.kingsrook.qbits.edi.x12.X12TransactionSet; +import com.kingsrook.qbits.edi.x12.X12Writer; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn; +import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType; +import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData; +import com.kingsrook.qqq.backend.core.model.metadata.tables.Association; +import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; +import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession; +import com.kingsrook.qqq.backend.module.rdbms.jdbc.ConnectionManager; +import com.kingsrook.qqq.backend.module.rdbms.model.metadata.RDBMSBackendMetaData; + + +/******************************************************************************* + ** Seeded synthetic healthcare fixtures and host tables; never used in main code. + ** NPI 1234567893 is a documented checksum test value, not a provider identity. + *******************************************************************************/ +public final class HealthcareSupport +{ + /******************************************************************************* + ** Test-only schema with classified target fields and actual QQQ associations. + *******************************************************************************/ + public static void tables() throws Exception + { + table("sample_claim", "claimNumber", "memberId", "patientName", "diagnosis", "totalCharge:DECIMAL"); + table("sample_claim_line", "parentId:INTEGER", "lineNumber:INTEGER", "procedureCode", "charge:DECIMAL"); + table("sample_remittance", "trace", "payment:DECIMAL"); + table("sample_remittance_claim", "parentId:INTEGER", "claimNumber", "patientName", "memberId", "charge:DECIMAL", "paid:DECIMAL"); + table("sample_eligibility_request", "trace", "memberId", "patientName"); + table("sample_eligibility_response", "trace", "memberId", "patientName", "benefit", "serviceType"); + table("sample_claim_status", "trace", "memberId", "patientName", "claimNumber", "category", "statusCode"); + table("sample_enrollment", "reference"); + table("sample_enrollment_member", "parentId:INTEGER", "memberId", "patientName", "birthDate:DATE", "coverage"); + association("sample_claim", "sample_claim_line", "lines"); + association("sample_remittance", "sample_remittance_claim", "claims"); + association("sample_enrollment", "sample_enrollment_member", "members"); + var instance = QContext.getQInstance(); + EdiRuntime.current().config().withServiceSessionSupplier(() -> new QSystemUserSession(instance)); + } + + /******************************************************************************* + ** Define one table and its H2 schema; fields are STRING unless suffixed with a type. + *******************************************************************************/ + private static void table(String name, String... fields) throws Exception + { + var instance = QContext.getQInstance(); + String backend = EdiRuntime.current().config().getBackendName(); + var table = new QTableMetaData().withName(name).withLabel(name).withBackendName(backend).withPrimaryKeyField("id") + .withField(new QFieldMetaData("id", QFieldType.INTEGER).withBackendName("id")); + for(String field : fields) + { + String[] parts = field.split(":"); + table.withField(new QFieldMetaData(parts[0], parts.length > 1 ? QFieldType.valueOf(parts[1]) : QFieldType.STRING).withBackendName(QInstanceEnricher.inferBackendName(parts[0])) + .withSupplementalMetaData(new com.kingsrook.qbits.edi.security.EdiClassifications(parts[0].equals("parentId") ? "CONFIDENTIAL" : "PHI"))); + } + instance.addTable(table); + try(Connection connection = ConnectionManager.getConnection((RDBMSBackendMetaData) instance.getBackend(backend)); var statement = connection.createStatement()) + { + statement.execute(H2Support.ddl(table, false)); + } + } + + /******************************************************************************* + ** Join-backed child records, as the retail samples do. + *******************************************************************************/ + private static void association(String parent, String child, String name) + { + var instance = QContext.getQInstance(); + String join = parent + "_" + name; + instance.getTable(parent).withAssociation(new Association().withName(name).withAssociatedTableName(child).withJoinName(join)); + instance.addJoin(new QJoinMetaData().withName(join).withType(JoinType.ONE_TO_MANY).withLeftTable(parent).withRightTable(child).withJoinOn(new JoinOn("id", "parentId"))); + } + + /******************************************************************************* + ** Version and group identifiers for the eight original baselines. + *******************************************************************************/ + public static String version(String key) + { + return (switch(key) + { + case "837P" -> "005010X222A1"; + case "837I" -> "005010X223A2"; + case "835" -> "005010X221A1"; + case "270", "271" -> "005010X279A1"; + case "276", "277" -> "005010X212"; + default -> "005010X220A1"; + }); + } + + /******************************************************************************* + ** Functional identifiers are routing metadata, never patient information. + *******************************************************************************/ + public static String group(String key) + { + return (switch(key) + { + case "837P", "837I" -> "HC"; + case "835" -> "HP"; + case "270" -> "HS"; + case "271" -> "HB"; + case "276" -> "HR"; + case "277" -> "HN"; + default -> "BE"; + }); + } + + /******************************************************************************* + ** Reproducible member and claim references; no production names or identifiers. + *******************************************************************************/ + public static String body(String key, long seed) + { + var random = new java.util.Random(seed); + String member = String.format("SYN%06d", random.nextInt(1000000)); + String claim = String.format("SYNCLM%06d", random.nextInt(1000000)); + if(key.startsWith("837")) + { + boolean inpatient = key.equals("837I"); + return ("BHT*0019*00*SYNBATCH*20261015*1200*CH~" + + "NM1*41*2*Synthetic Submitter*****46*SYN~NM1*40*2*Synthetic Receiver*****46*SYN~" + + "HL*1**20*1~NM1*85*2*Synthetic Practice*****XX*1234567893~N3*1 Synthetic Way~N4*Testville*IL*60000~" + + "HL*2*1*22*0~SBR*P*18*******CI~NM1*IL*1*Testpatient Alpha*****MI*" + member + "~DMG*D8*19800101*U~NM1*PR*2*Synthetic Plan*****PI*SYNPLAN~" + + "CLM*" + claim + "*150***" + (inpatient ? "11:A:1" : "11:B:1") + "~" + + (inpatient ? "DTP*434*RD8*20261014-20261015~" : "") + "HI*ABK:A123~" + + "LX*1~" + (inpatient ? "SV2*0300*HC:S0001*100*UN*1~" : "SV1*HC:S0001*100*UN*1~") + "DTP*472*D8*20261015~" + + "LX*2~" + (inpatient ? "SV2*0300*HC:S0002*50*UN*1~" : "SV1*HC:S0002*50*UN*1~") + "DTP*472*D8*20261015~"); + } + if(key.equals("835")) + { + return ("BPR*I*115*C*CHK************20261015~TRN*1*SYNTRACE" + seed + "~N1*PR*Synthetic Plan~N1*PE*Synthetic Practice~" + + "LX*1~CLP*" + claim + "*1*150*120*30~CAS*PR*1*10~NM1*QC*1*Testpatient Alpha~NM1*IL*1*Testpatient Alpha*****MI*" + member + "~" + + "SVC*HC:S0001*100*80~DTM*472*20261015~CAS*CO*45*20~SVC*HC:S0002*50*40~DTM*472*20261015~" + + "PLB*1234567893*20261231*WO:SYNADJ*5~"); + } + if(key.equals("834")) + { + return ("BGN*00*SYNENROLL" + seed + "*20261015*1200~N1*P5*Synthetic Sponsor~N1*IN*Synthetic Plan~" + + "INS*Y*18*021~REF*0F*" + member + "~NM1*IL*1*Testpatient Alpha~DMG*D8*19800101*U~HD*021**HLT~DTP*348*D8*20261015~"); + } + String head = "BHT*0022*13*SYNBATCH*20261015*1200~HL*1**20*1~NM1*PR*2*Synthetic Plan*****PI*SYNPLAN~" + + "HL*2*1*21*1~NM1*1P*2*Synthetic Practice*****XX*1234567893~HL*3*2*22*0~"; + return (head + (key.equals("270") || key.equals("271") ? "TRN*1*SYNTRACE" + seed + "~" : "") + + "NM1*IL*1*Testpatient Alpha*****MI*" + member + "~" + + (key.equals("270") ? "EQ*30~" : key.equals("271") ? "EB*1**30~" : "TRN*1*SYNTRACE" + seed + "~" + + (key.equals("277") ? "STC*A1:19*20261015~" : "") + "REF*EJ*" + claim + "~")); + } + + /******************************************************************************* + ** Wrap raw synthetic segments using real ISA widths and counted ST/SE boundaries. + *******************************************************************************/ + public static String interchange(String key, int control, String body) + { + String version = version(key); + String set = key.startsWith("837") ? "837" : key; + String isa = new X12Writer().write(List.of(X12Segment.of("ISA", 0, "00", "", "00", "", "ZZ", "CEDAR", "ZZ", "QQQTEST", "261015", "1200", "^", "00501", String.format("%09d", control), "0", "T", ":")), X12Separators.DEFAULT, ""); + return (isa + "GS*" + group(key) + "*CEDAR*QQQTEST*20261015*1200*" + control + "*X*" + version + "~ST*" + set + "*0001*" + version + "~" + + body + "SE*" + (body.split("~").length + 2) + "*0001~GE*1*" + control + "~IEA*1*" + String.format("%09d", control) + "~"); + } + + /******************************************************************************* + ** Seed the healthcare partner and sample agreement documents as normal records. + *******************************************************************************/ + public static QRecord configure() throws Exception + { + return (SystemWrites.call(() -> + { + QRecord partner = SystemWrites.insert("edi_partner", new QRecord().withValue("name", "Cedar Health Clearinghouse").withValue("code", "CEDAR") + .withValue("status", "ACTIVE").withValue("containsPhi", true).withValue("contactEmail", "cedar@example.invalid")); + var locals = EdiRecords.query("edi_identity", EdiRecords.equal("interchangeId", "QQQTEST")); + QRecord local = locals.isEmpty() ? SystemWrites.insert("edi_identity", new QRecord().withValue("standard", "X12").withValue("interchangeQualifier", "ZZ").withValue("interchangeId", "QQQTEST").withValue("applicationCode", "QQQTEST")) : locals.getFirst(); + QRecord remote = SystemWrites.insert("edi_identity", new QRecord().withValue("partnerId", partner.getValue("id")).withValue("standard", "X12").withValue("interchangeQualifier", "ZZ").withValue("interchangeId", "CEDAR").withValue("applicationCode", "CEDAR")); + QRecord transport = SystemWrites.insert("edi_transport", new QRecord().withValue("partnerId", partner.getValue("id")).withValue("name", "cedar-gateway").withValue("type", "GATEWAY") + .withValue("direction", "BOTH").withValue("gatewayEndpointName", "cedar-outbox").withValue("isActive", true)); + QRecord agreement = SystemWrites.insert("edi_agreement", new QRecord().withValue("partnerId", partner.getValue("id")).withValue("localIdentityId", local.getValue("id")).withValue("partnerIdentityId", remote.getValue("id")) + .withValue("standard", "X12").withValue("usageIndicator", "T").withValue("applyTestDocuments", true).withValue("isActive", true).withValue("interchangeVersion", "00501") + .withValue("inboundTransportId", transport.getValue("id")).withValue("outboundTransportId", transport.getValue("id"))); + for(String key : List.of("837P", "837I", "835", "270", "271", "276", "277", "834")) + { + QRecord map = LogisticsSupport.installMap(key, version(key)); + SystemWrites.insert("edi_agreement_document", new QRecord().withValue("agreementId", agreement.getValue("id")).withValue("direction", map.getValue("direction")) + .withValue("transactionSet", map.getValue("transactionSet")).withValue("version", version(key)).withValue("functionalIdCode", group(key)) + .withValue("guideId", map.getValue("guideId")).withValue("mapId", map.getValue("id")).withValue("ackExpectation", "999").withValue("ackLevel", "ALL_TRANSACTIONS") + .withValue("sourceTableName", map.getValueString("direction").equals("OUTBOUND") ? map.getValue("tableName") : null).withValue("isActive", true)); + } + return (agreement); + })); + } + + /******************************************************************************* + ** Normal durable intake and parsing, shared with the demo. + *******************************************************************************/ + public static void receive(String key, int control, String body) throws Exception + { + new com.kingsrook.qbits.edi.flow.InboundSink(EdiRuntime.current().payloads()).receive(new com.kingsrook.qbits.edi.flow.InboundPayload("GATEWAY", "health-" + control, + "synthetic-healthcare.edi", "127.0.0.1", new ByteArrayInputStream(interchange(key, control, body).getBytes(StandardCharsets.US_ASCII)))); + new com.kingsrook.qbits.edi.flow.InboxWorker().runOnce(); + } +} diff --git a/src/test/java/com/kingsrook/qbits/edi/HealthcareTest.java b/src/test/java/com/kingsrook/qbits/edi/HealthcareTest.java new file mode 100644 index 0000000..4ec7ff5 --- /dev/null +++ b/src/test/java/com/kingsrook/qbits/edi/HealthcareTest.java @@ -0,0 +1,279 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.kingsrook.qbits.edi; + + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import com.kingsrook.qbits.edi.ack.AckLevel; +import com.kingsrook.qbits.edi.ack.Ack999Builder; +import com.kingsrook.qbits.edi.guide.BuiltInGuideLoader; +import com.kingsrook.qbits.edi.guide.GuideValidator; +import com.kingsrook.qbits.edi.guide.HealthcareValidation; +import com.kingsrook.qbits.edi.guide.ValidationFinding; +import com.kingsrook.qbits.edi.map.InboundMapper; +import com.kingsrook.qbits.edi.map.OutboundMapper; +import com.kingsrook.qbits.edi.map.TransformRegistry; +import com.kingsrook.qbits.edi.storage.EdiRecords; +import com.kingsrook.qbits.edi.support.SystemWrites; +import com.kingsrook.qbits.edi.x12.X12Separators; +import com.kingsrook.qbits.edi.x12.X12Writer; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import static org.assertj.core.api.Assertions.assertThat; + + +/******************************************************************************* + ** Original healthcare baselines, balances, situational failures and classified flows. + *******************************************************************************/ +class HealthcareTest +{ + /******************************************************************************* + ** Restore the calling thread after database tests. + *******************************************************************************/ + @AfterEach + void clear() + { + QContext.clear(); + } + + /******************************************************************************* + ** Validate a synthetic body including its real envelopes and transaction controls. + *******************************************************************************/ + private static List validate(String key, String body) throws Exception + { + var tx = LogisticsSupport.read(HealthcareSupport.interchange(key, 1, body)).groups().getFirst().transactions().getFirst(); + assertThat(tx.findings()).isEmpty(); + return (new GuideValidator().validate(tx, new BuiltInGuideLoader().resource(key + "-" + HealthcareSupport.version(key)), true)); + } + + /******************************************************************************* + ** All eight baseline guides accept their seeded fixtures, including format checks. + *******************************************************************************/ + @ParameterizedTest + @ValueSource(strings = {"837P", "837I", "835", "270", "271", "276", "277", "834"}) + void baseline(String key) throws Exception + { + assertThat(validate(key, HealthcareSupport.body(key, 123))).isEmpty(); + } + + /******************************************************************************* + ** Every inbound map writes the synthetic identifiers and associated records. + *******************************************************************************/ + @ParameterizedTest + @ValueSource(strings = {"837P", "837I", "835", "271", "277", "834"}) + void inboundMap(String key) throws Exception + { + var tx = LogisticsSupport.read(HealthcareSupport.interchange(key, 1, HealthcareSupport.body(key, 123))).groups().getFirst().transactions().getFirst(); + List rules = new ArrayList<>(); + var map = LogisticsSupport.definition(key, HealthcareSupport.version(key), rules); + var result = new InboundMapper(new TransformRegistry()).map(tx, new BuiltInGuideLoader().resource(key + "-" + HealthcareSupport.version(key)), map, rules); + assertThat(result.businessKey()).startsWith("SYN"); + if(key.startsWith("837")) + { + assertThat(result.record().getValueString("patientName")).isEqualTo("Testpatient Alpha"); + assertThat(result.record().getValueBigDecimal("totalCharge")).isEqualByComparingTo("150"); + assertThat(result.record().getAssociatedRecords().get("lines")).hasSize(2); + assertThat(result.record().getAssociatedRecords().get("lines").getLast().getValueBigDecimal("charge")).isEqualByComparingTo("50"); + } + if(key.equals("835")) + { + assertThat(result.record().getAssociatedRecords().get("claims")).hasSize(1); + } + if(key.equals("834")) + { + assertThat(result.record().getAssociatedRecords().get("members")).hasSize(1); + } + } + + /******************************************************************************* + ** Missing situational data produces SNIP 4 findings, never submitted values. + *******************************************************************************/ + @Test + void situationalFailures() throws Exception + { + String body = HealthcareSupport.body("837P", 123); + String dependent = body.replace("SBR*P*18", "SBR*P*19").replace("~CLM*", "~HL*3*2*23*0~PAT*19~NM1*QC*1*Testpatient Beta~CLM*"); + assertThat(validate("837P", dependent)).isEmpty(); + List dependentRules = new ArrayList<>(); + var fixture = LogisticsSupport.definition("837P", "005010X222A1", dependentRules); + var dependentTx = LogisticsSupport.read(HealthcareSupport.interchange("837P", 1, dependent)).groups().getFirst().transactions().getFirst(); + var dependentMap = new InboundMapper(new TransformRegistry()).map(dependentTx, new BuiltInGuideLoader().resource("837P-005010X222A1"), fixture, dependentRules); + assertThat(dependentMap.record().getValueString("patientName")).isEqualTo("Testpatient Beta"); + for(String invalid : List.of(body.replace("SBR*P*18", "SBR*P*19"), dependent.replace("SBR*P*19", "SBR*P*18"), + dependent.replace("PAT*19~", ""), body.replace("HI*ABK:A123~", ""), body.replaceFirst("DTP\\*472\\*D8\\*20261015~", ""))) + { + assertThat(validate("837P", invalid)).anyMatch(f -> f.snipLevel() == 4); + } + assertThat(validate("837I", HealthcareSupport.body("837I", 123).replace("DTP*434*RD8*20261014-20261015~", ""))).anyMatch(f -> f.snipLevel() == 4); + assertThat(validate("834", HealthcareSupport.body("834", 123).replace("DMG*D8*19800101*U~", ""))).anyMatch(f -> f.snipLevel() == 4); + String claimDate = body.replace("HI*", "DTP*472*D8*20261015~HI*").replace("~DTP*472*D8*20261015~LX", "~LX"); + assertThat(validate("837P", claimDate)).isEmpty(); + } + + /******************************************************************************* + ** Exact decimal balancing and implementation acknowledgement text with no values. + *******************************************************************************/ + @Test + void balanceAndExact999() throws Exception + { + String body = HealthcareSupport.body("837P", 123).replace("*150***", "*150.01***"); + var findings = validate("837P", body); + assertThat(findings).singleElement().satisfies(f -> assertThat(f.snipLevel()).isEqualTo(3)); + var group = LogisticsSupport.read(HealthcareSupport.interchange("837P", 1, body)).groups().getFirst(); + var ack = new Ack999Builder().build(group, Map.of("0001", findings), AckLevel.ALL_TRANSACTIONS, "0002"); + assertThat(new X12Writer().write(ack.segments(), X12Separators.DEFAULT, "")).isEqualTo( + "ST*999*0002*005010X231A1~AK1*HC*1*005010X222A1~AK2*837*0001*005010X222A1~IK3*CLM*14*2300*8~IK4*2**7~CTX*SITUATIONAL TRIGGER*CLM*14*2300*2~IK5*E~AK9*E*1*1*1~SE*9*0002~"); + assertThat(new GuideValidator().validate(ack, new BuiltInGuideLoader().resource("999-005010X231A1"), false)).isEmpty(); + assertThat(validate("837I", HealthcareSupport.body("837I", 123).replace("*150***", "*149.99***"))).anyMatch(f -> f.snipLevel() == 3); + String remit = HealthcareSupport.body("835", 123); + assertThat(validate("835", remit.replace("BPR*I*115", "BPR*I*115.01"))).anyMatch(f -> f.snipLevel() == 3 && f.segmentId().equals("BPR")); + assertThat(validate("835", remit.replace("CAS*CO*45*20", "CAS*CO*45*19.99"))).anyMatch(f -> f.snipLevel() == 3 && f.segmentId().equals("CLP")); + assertThat(validate("835", remit.replace("BPR*I*115", "BPR*I*125").replace("WO:SYNADJ*5", "WO:SYNADJ*-5"))).isEmpty(); + assertThat(validate("835", remit.replace("CAS*CO*45*20", "CAS*CO*45*10*1*94*10"))).isEmpty(); + } + + /******************************************************************************* + ** Licensed membership is optional, format checks and NPI checksum are deterministic. + *******************************************************************************/ + @Test + void formatsAndExternalCodes() throws Exception + { + assertThat(HealthcareValidation.validNpi("1234567893")).isTrue(); + assertThat(HealthcareValidation.validNpi("1234567890")).isFalse(); + String body = HealthcareSupport.body("837P", 123); + for(String invalid : List.of(body.replace("1234567893", "1234567890"), body.replace("60000", "ZIPBAD"), body.replace("ABK:A123", "ABK:BAD!"))) + { + assertThat(validate("837P", invalid)).anyMatch(f -> f.snipLevel() == 5); + } + var calls = new ArrayList(); + var config = new EdiQBitConfig().withExternalCodeSetProvider((set, code, date) -> + { + calls.add(set + ":" + date); + return (!set.equals("ICD10CM")); + }); + var tx = LogisticsSupport.read(HealthcareSupport.interchange("837P", 1, body)).groups().getFirst().transactions().getFirst(); + var result = new GuideValidator(config).validate(tx, new BuiltInGuideLoader().resource("837P-005010X222A1"), true); + assertThat(result).anyMatch(f -> f.snipLevel() == 5 && f.segmentId().equals("HI")); + assertThat(calls).contains("ICD10CM:2026-10-15", "HCPCS:2026-10-15"); + } + + /******************************************************************************* + ** Outbound eligibility and claim status requests use only their classified sources. + *******************************************************************************/ + @ParameterizedTest + @ValueSource(strings = {"270", "276"}) + void outboundMap(String key) throws Exception + { + H2Support.create(); + List rules = new ArrayList<>(); + QRecord map = LogisticsSupport.definition(key, HealthcareSupport.version(key), rules); + var tx = new OutboundMapper().build(new QRecord().withValue("trace", "SYNTRACE123").withValue("memberId", "SYN000123") + .withValue("patientName", "Testpatient Alpha").withValue("claimNumber", "SYNCLM123"), map, rules, "0001"); + assertThat(new GuideValidator().validate(tx, new BuiltInGuideLoader().resource(key + "-" + HealthcareSupport.version(key)), true)).isEmpty(); + } + + /******************************************************************************* + ** A claim cannot borrow charges, dates or patient loops from a neighboring claim. + *******************************************************************************/ + @Test + void independentClaimsAndSituational999() throws Exception + { + String body = HealthcareSupport.body("837P", 123); + String claim = body.substring(body.indexOf("CLM*")); + String two = body + claim.replace("*150***", "*151***"); + assertThat(validate("837P", two)).singleElement().satisfies(f -> assertThat(f.snipLevel()).isEqualTo(3)); + String offsetting = body.replace("*150***", "*149***") + claim.replace("*150***", "*151***"); + assertThat(validate("837P", offsetting).stream().filter(f -> f.snipLevel() == 3)).hasSize(2); + String missingDate = body + claim.replace("DTP*472*D8*20261015~", ""); + assertThat(validate("837P", missingDate).stream().filter(f -> f.snipLevel() == 4)).hasSize(2); + String missingPatient = body.replace("SBR*P*18", "SBR*P*19"); + var group = LogisticsSupport.read(HealthcareSupport.interchange("837P", 1, missingPatient)).groups().getFirst(); + String ack = new X12Writer().write(new Ack999Builder().build(group, Map.of("0001", validate("837P", missingPatient)), AckLevel.ALL_TRANSACTIONS, "0002").segments(), X12Separators.DEFAULT, ""); + assertThat(ack).isEqualTo("ST*999*0002*005010X231A1~AK1*HC*1*005010X222A1~AK2*837*0001*005010X222A1~IK3*SBR*10*2000B*8~CTX*SITUATIONAL TRIGGER*SBR*10*2000B*2~IK5*E~AK9*E*1*1*1~SE*8*0002~"); + assertThat(validate("271", HealthcareSupport.body("271", 123).replace("EB*1**30~", ""))).anyMatch(f -> f.snipLevel() == 4); + assertThat(validate("271", HealthcareSupport.body("271", 123).replace("EB*1**30~", "AAA*Y**72*C~"))).isEmpty(); + } + + /******************************************************************************* + ** External code-list source and name survive normalization; the home reflects the SPI. + *******************************************************************************/ + @Test + void externalRepositoryAndHome() throws Exception + { + H2Support.create(); + var source = new BuiltInGuideLoader().resource("837P-005010X222A1"); + Integer id = new com.kingsrook.qbits.edi.guide.GuideRepository().save(source, "synthetic", 1); + var guide = new com.kingsrook.qbits.edi.guide.GuideRepository().load(id); + assertThat(guide.segments().stream().flatMap(s -> s.elements().stream()).filter(e -> "EXTERNAL".equals(e.source()))) + .allMatch(e -> e.externalCodeSet() != null && e.codes().isEmpty()); + var runtime = EdiRuntime.current(); + var tx = LogisticsSupport.read(HealthcareSupport.interchange("837P", 1, HealthcareSupport.body("837P", 123))).groups().getFirst().transactions().getFirst(); + assertThat(runtime.validator().validate(tx, guide, true)).isEmpty(); + runtime.config().withExternalCodeSetProvider((set, code, date) -> false); + assertThat(runtime.validator().validate(tx, guide, true)).anyMatch(f -> f.snipLevel() == 5); + var widget = QContext.getQInstance().getWidget("edi_home_system"); + String json = com.kingsrook.qqq.backend.core.utils.JsonUtils.toJson(new com.kingsrook.qbits.edi.app.EdiHome() + .render(new com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput().withWidgetMetaData(widget))); + assertThat(json).contains("Provider configured"); + runtime.config().withExternalCodeSetProvider(null); + json = com.kingsrook.qqq.backend.core.utils.JsonUtils.toJson(new com.kingsrook.qbits.edi.app.EdiHome() + .render(new com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput().withWidgetMetaData(widget))); + assertThat(json).contains("Format only"); + } + + /******************************************************************************* + ** Real persistence honors defaults, PHI keys, 999 routing and accepted-with-error apply. + *******************************************************************************/ + @Test + void healthcarePipeline() throws Exception + { + H2Support.create(); + HealthcareSupport.tables(); + EdiRuntime.current().initialize(); + HealthcareSupport.configure(); + int control = 100; + for(String key : List.of("837P", "837I", "835", "271", "277", "834")) + { + HealthcareSupport.receive(key, ++control, HealthcareSupport.body(key, control)); + } + HealthcareSupport.receive("837P", ++control, HealthcareSupport.body("837P", control).replace("*150***", "*151***")); + var rows = EdiRecords.query("edi_transaction", new QQueryFilter()); + assertThat(rows.stream().filter(r -> "INBOUND".equals(r.getValueString("direction"))).toList()).hasSize(7).allSatisfy(r -> + { + assertThat(r.getValueString("status")).isEqualTo("APPLIED"); + assertThat(r.getValueString("phiBusinessKey")).startsWith("SYN"); + assertThat(r.getValueString("businessKey")).isNull(); + assertThat(r.getValueBoolean("containsPhi")).isTrue(); + }); + assertThat(rows).anyMatch(r -> "E".equals(r.getValueString("ackStatus"))); + assertThat(rows.stream().filter(r -> "OUTBOUND".equals(r.getValueString("direction"))).toList()).hasSize(7).allMatch(r -> "999".equals(r.getValueString("transactionSet"))); + assertThat(EdiRecords.query("sample_claim_line", new QQueryFilter())).hasSize(6); + assertThat(EdiRecords.query("edi_transaction_error", new QQueryFilter())).allMatch(r -> r.getValue("valueExcerpt") == null); + assertThat(EdiRecords.query("edi_agreement_document", new QQueryFilter())).allMatch(r -> Boolean.TRUE.equals(r.getValueBoolean("containsPhi"))); + } +} diff --git a/src/test/java/com/kingsrook/qbits/edi/PhiCanaryTest.java b/src/test/java/com/kingsrook/qbits/edi/PhiCanaryTest.java new file mode 100644 index 0000000..200f8ff --- /dev/null +++ b/src/test/java/com/kingsrook/qbits/edi/PhiCanaryTest.java @@ -0,0 +1,481 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2025. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.kingsrook.qbits.edi; + + +import java.io.ByteArrayInputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.kingsrook.qbits.edi.demo.EdiDemoServer; +import com.kingsrook.qbits.edi.flow.AckMonitor; +import com.kingsrook.qbits.edi.flow.InboundSink; +import com.kingsrook.qbits.edi.flow.InboxWorker; +import com.kingsrook.qbits.edi.flow.SendWorker; +import com.kingsrook.qbits.edi.metadata.EdiTables; +import com.kingsrook.qbits.edi.processes.EdiProcesses; +import com.kingsrook.qbits.edi.security.EdiClassifications; +import com.kingsrook.qbits.edi.storage.EdiRecords; +import com.kingsrook.qbits.edi.storage.PayloadCrypto; +import com.kingsrook.qbits.edi.storage.PreviewDownloadBackend; +import com.kingsrook.qbits.edi.storage.RawDocumentDownload; +import com.kingsrook.qbits.edi.support.SystemWrites; +import com.kingsrook.qbits.edi.transport.HttpsRouteProvider; +import com.kingsrook.qbits.edi.transport.gateway.EdiFileGatewayConsumer; +import com.kingsrook.qbits.edi.transport.gateway.standin.FileGatewayOutbox; +import com.kingsrook.qbits.edi.transport.gateway.standin.InboundFile; +import com.kingsrook.qbits.edi.transport.gateway.standin.OutboxFileRef; +import com.kingsrook.qqq.backend.core.actions.dashboard.RenderWidgetAction; +import com.kingsrook.qqq.backend.core.actions.tables.QueryAction; +import com.kingsrook.qqq.backend.core.context.QContext; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput; +import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; +import com.kingsrook.qqq.backend.core.model.actions.tables.storage.StorageInput; +import com.kingsrook.qqq.backend.core.model.actions.widgets.RenderWidgetInput; +import com.kingsrook.qqq.backend.core.model.data.QRecord; +import com.kingsrook.qqq.backend.core.utils.JsonUtils; +import io.javalin.Javalin; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.layout.PatternLayout; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + + +/******************************************************************************* + ** End-to-end PHI egress scanner. Only explicit PHI columns may retain sentinels. + ** RC.5 has no secured-data tier resolver; FULL exports and positional egress run here. + *******************************************************************************/ +class PhiCanaryTest +{ + private final List markers = new ArrayList<>(); + private final ObjectMapper json = new ObjectMapper(); + + /******************************************************************************* + ** Every five-character window is enough to detect any longer leaked substring. + *******************************************************************************/ + private void scan(String output, String channel) + { + for(String marker : markers) + { + for(int i = 0; i <= marker.length() - 5; i++) + { + String window = marker.substring(i, i + 5); + int at = output.indexOf(window); + assertThat(at).as("PHI substring in " + channel + (at < 0 ? "" : ": " + output.substring(0, Math.min(120, output.length())) + " ... " + output.substring(Math.max(0, at - 60), Math.min(output.length(), at + 80)))).isNegative(); + } + } + } + + /******************************************************************************* + ** Exempt only values and display values of metadata-classified PHI record columns. + ** Labels, messages, metadata, context and every other JSON property remain scanned. + *******************************************************************************/ + private void exemptColumns(JsonNode node, String table) + { + if(node.isObject()) + { + JsonNode records = node.get("records"); + if(records != null && records.isArray()) + { + for(JsonNode record : records) + { + for(String key : List.of("values", "displayValues")) + { + if(record.get(key) instanceof ObjectNode values) + { + for(var field : QContext.getQInstance().getTable(table).getFields().values()) + { + var classification = field.getSupplementalMetaData("edi.classification"); + if(classification instanceof EdiClassifications classified && classified.getClassification().equals("PHI")) + { + values.remove(field.getName()); + } + } + } + } + } + } + } + node.elements().forEachRemaining(child -> exemptColumns(child, table)); + } + + /******************************************************************************* + ** Scan all properties of the real serialized response, with narrow column exceptions. + *******************************************************************************/ + private void response(Object output, String table, String channel) throws Exception + { + JsonNode node = json.readTree(JsonUtils.toJson(output)); + if(table != null) + { + exemptColumns(node, table); + } + scan(node.toString(), channel); + } + + /******************************************************************************* + ** Force distinct field sentinels twice, across both ingress contracts and replays. + *******************************************************************************/ + @Test + void allAvailableEgress() throws Exception + { + try(EdiDemoServer demo = new EdiDemoServer(); HttpClient client = HttpClient.newHttpClient()) + { + demo.seed(); + var instance = demo.defineQInstance(); + CapturedAlerts.outputs.clear(); + com.kingsrook.qqq.backend.core.alerts.AlertsInstanceMetaData.ofOrWithNew(instance).withSink(new com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference(CapturedAlerts.class)); + // A test-only widened diagnosis field allows the long marker through mapping; + // its invalid code shape still exercises SNIP 5, and separate documents force SNIP 2. + SystemWrites.call(() -> + { + for(QRecord segment : EdiRecords.query("edi_guide_segment", EdiRecords.equal("segmentId", "HI"))) + { + for(QRecord element : EdiRecords.query("edi_guide_element", EdiRecords.equal("guideSegmentId", segment.getValue("id")))) + { + SystemWrites.update("edi_guide_element", new QRecord().withValue("id", element.getValue("id")).withValue("maxLength", 80)); + } + } + SystemWrites.insert("edi_transport", new QRecord().withValue("name", "canary-ingress").withValue("type", "HTTPS").withValue("direction", "INBOUND") + .withValue("isActive", true).withValue("httpsInboundAuth", "BEARER").withValue("httpsSecretEnvVar", "SYNTHETIC_HTTP_KEY")); + return (null); + }); + EdiRuntime.current().config().withSecretsResolver(name -> name.equals("SYNTHETIC_HTTP_KEY") ? "synthetic-auth" : Base64.getEncoder().encodeToString(new byte[32])); + var provider = new HttpsRouteProvider(); + provider.setQInstance(instance); + var server = Javalin.create(provider::acceptJavalinConfig).start(0); + var capture = new CapturedLogs(); + var logger = (org.apache.logging.log4j.core.Logger) LogManager.getRootLogger(); + capture.start(); + logger.addAppender(capture); + List transports = new ArrayList<>(); + FileGatewayOutbox.register((endpoint, name, stream, size) -> + { + transports.add(new String(stream.readAllBytes(), StandardCharsets.US_ASCII)); + return (new OutboxFileRef(transports.size(), endpoint, name, name, size, null, Instant.now())); + }); + try + { + int control = 800; + List received = new ArrayList<>(); + for(int variant = 0; variant < 2; variant++) + { + List fields = new ArrayList<>(); + for(int field = 0; field < 4; field++) + { + String marker = "CANARY-PHI-" + UUID.randomUUID().toString().substring(0, 8).replaceAll("(.)", "Z$1"); + fields.add(marker); + markers.add(marker); + } + for(String key : List.of("837P", "835")) + { + String body = HealthcareSupport.body(key, 123).replace("Testpatient Alpha", fields.get(0)) + .replaceAll("SYN[0-9]{6}", fields.get(1)).replaceAll("SYNCLM[0-9]{6}", fields.get(2)); + if(key.equals("837P")) + { + body = body.replace("ABK:A123", "ABK:" + fields.get(3)).replace("*150***", "*150.01***").replace("SBR*P*18", "SBR*P*19"); + } + else + { + body = body.replace("SVC*HC:S0001", "REF*DX*" + fields.get(3) + "~SVC*HC:S0001").replace("BPR*I*115", "BPR*I*115.01"); + } + String raw = HealthcareSupport.interchange(key, ++control, body); + byte[] bytes = raw.getBytes(StandardCharsets.US_ASCII); + if(variant == 0) + { + var file = new InboundFile(control, "cedar-inbox", "synthetic-user", "synthetic-user", "127.0.0.1", "/", fields.get(1) + ".edi", + bytes.length, PayloadCrypto.digest(bytes), Instant.now(), null, null, () -> new ByteArrayInputStream(bytes)); + response(new EdiFileGatewayConsumer(new InboundSink(EdiRuntime.current().payloads())).accept(file), null, "gateway reply"); + } + else + { + var reply = client.send(HttpRequest.newBuilder(URI.create("http://localhost:" + server.port() + "/qqq/hooks/edi/https/canary-ingress")) + .header("Authorization", "Bearer synthetic-auth").POST(HttpRequest.BodyPublishers.ofByteArray(bytes)).build(), HttpResponse.BodyHandlers.ofString()); + assertThat(reply.statusCode()).isEqualTo(202); + scan(reply.body() + reply.headers(), "HTTPS response"); + } + assertThat(new InboxWorker().runOnce()).isEqualTo(1); + var transaction = rows("edi_transaction").stream().filter(r -> fields.get(2).equals(r.getValueString("phiBusinessKey")) || key.equals("835") && "SYNTRACE123".equals(r.getValueString("phiBusinessKey"))) + .reduce((a, b) -> b).orElseThrow(); + assertThat(transaction.getValueString("status")).isEqualTo("APPLIED"); + received.add(transaction); + // Force a structural rejection with the same markers in the same fields. + HealthcareSupport.receive(key, ++control, body.replace(key.equals("837P") ? "SV1*HC:S0001*100" : "BPR*I*115.01", key.equals("837P") ? "SV1*HC:S0001*BAD" : "BPR*I*BAD")); + } + } + assertThat(rows("edi_transaction_error")).extracting(r -> r.getValueInteger("snipLevel")).contains(2, 3, 4); + QRecord first = received.getFirst(); + QRecord archived = rows("edi_interchange").stream().filter(r -> first.getValue("interchangeId").equals(r.getValue("id"))).findFirst().orElseThrow(); + QRecord sample = SystemWrites.call(() -> SystemWrites.insert("edi_map_sample", new QRecord().withValue("mapId", first.getValue("mapId")) + .withValue("name", "Synthetic PHI preview").withValue("payloadRef", archived.getValue("payloadRef")).withValue("expectedResultJson", "{\"clinicalKey\":\"" + markers.getFirst() + "\"}"))); + process("edi_map_sample_preview", sample); + for(QRecord transaction : received) + { + process("edi_transaction_reprocess", transaction); + assertThat(new InboxWorker().runOnce()).isEqualTo(1); + } + for(QRecord exception : rows("edi_exception")) + { + process("edi_exception_resolve", exception); + } + while(new SendWorker().runOnce() > 0) + { + // Capture all eligible acknowledgement transports. + } + QRecord ack = rows("edi_outbound_message").stream().filter(r -> r.getValue("ackForGroupId") != null && "SENT".equals(r.getValueString("status"))).reduce((a, b) -> b).orElseThrow(); + process("edi_outbound_message_resend", ack); + new SendWorker().runOnce(); + assertThat(transports).isNotEmpty(); + transports.forEach(payload -> scan(payload, "non-PHI acknowledgement transport")); + // Actual overdue alerts, with complete serialized signals captured by the sink. + QRecord group = rows("edi_functional_group").getFirst(); + SystemWrites.call(() -> { SystemWrites.update("edi_functional_group", new QRecord().withValue("id", group.getValue("id")).withValue("ackStatus", "PENDING").withValue("ackDueAt", Instant.now().minusSeconds(60))); return (null); }); + new AckMonitor().runOnce(); + assertThat(CapturedAlerts.outputs).isNotEmpty(); + CapturedAlerts.outputs.forEach(output -> scan(output, "alerts")); + for(var table : instance.getTables().values()) + { + if(table.getName().startsWith("edi_") && !table.getName().equals(PreviewDownloadBackend.TABLE)) + { + var output = new QueryAction().execute(new QueryInput(table.getName()).withFilter(new QQueryFilter()).withShouldFetchHeavyFields(true)); + response(output, table.getName(), "export " + table.getName()); + } + } + int rendered = 0; + for(var widget : instance.getWidgets().values()) + { + var join = EdiTables.joins().stream().filter(j -> j.getName().equals(widget.getName())).findFirst(); + if(join.isEmpty()) + { + response(new RenderWidgetAction().execute(new RenderWidgetInput().withWidgetMetaData(widget)), null, widget.getName()); + } + else + { + for(QRecord parent : rows(join.get().getLeftTable())) + { + var params = new HashMap<>(Map.of("tableName", join.get().getLeftTable(), "id", parent.getValueString("id"))); + response(new RenderWidgetAction().execute(new RenderWidgetInput().withWidgetMetaData(widget).withUrlParams(params)), join.get().getRightTable(), widget.getName()); + } + } + rendered++; + } + assertThat(rendered).isEqualTo(instance.getWidgets().size()); + assertThat(capture.outputs).isNotEmpty(); + capture.outputs.forEach(output -> scan(output, "logs")); + // Ciphertext contains no plaintext marker, including after decoding the BLOB. + for(QRecord payload : rows("edi_payload")) + { + if(payload.getValue("ciphertext") instanceof byte[] bytes) + { + scan(new String(bytes, StandardCharsets.ISO_8859_1), "ciphertext bytes"); + } + } + // Raw access is the explicit authorized escape: bytes are streamed, event is value-free. + QRecord interchange = rows("edi_interchange").stream().filter(r -> received.getFirst().getValue("interchangeId").equals(r.getValue("id"))).findFirst().orElseThrow(); + var download = process("edi_interchange_download", interchange); + var storage = new StorageInput(PreviewDownloadBackend.TABLE).withReference(download.getValueString("storageReference")); + try(var stream = new PreviewDownloadBackend().getInputStream(storage)) + { + assertThat(new String(stream.readAllBytes(), StandardCharsets.US_ASCII)).contains(markers.getFirst()); + } + assertThat(rows("edi_event")).anyMatch(r -> "RAW_DOWNLOADED".equals(r.getValueString("type")) && r.getValueString("userName") != null); + response(new QueryAction().execute(new QueryInput("edi_event").withFilter(new QQueryFilter())), "edi_event", "download audit"); + assertThatThrownBy(() -> RawDocumentDownload.open("raw:edi_transaction:1")).hasMessageContaining("not authorized"); + capture.outputs.forEach(output -> scan(output, "logs after raw download")); + var fullSession = QContext.getQSession(); + try + { + QContext.setQSession(new com.kingsrook.qqq.backend.core.model.session.QSession()); + assertThatThrownBy(() -> new PreviewDownloadBackend().getInputStream(storage)).hasMessageContaining("not authorized"); + assertThatThrownBy(() -> RawDocumentDownload.open(storage.getReference())).hasMessageContaining("not authorized"); + } + finally + { + QContext.setQSession(fullSession); + } + hiddenExportsAndWidgets(); + capture.outputs.forEach(output -> scan(output, "logs after HIDDEN exports and widgets")); + } + finally + { + logger.removeAppender(capture); + capture.stop(); + server.stop(); + } + } + } + + /******************************************************************************* + ** RC.5 HIDDEN field locks are real enforcement, not test-side JSON redaction. + *******************************************************************************/ + private void hiddenExportsAndWidgets() throws Exception + { + var instance = QContext.getQInstance(); + var enforcement = instance.getEnforcePermissionsInCoreActions(); + instance.setEnforcePermissionsInCoreActions(true); + var fullSession = QContext.getQSession(); + var viewer = new com.kingsrook.qqq.backend.core.model.session.QSession().withPermissions("edi.admin").withSecurityKeyValue("edi.admin", true).withSecurityKeyValue("edi.guide.loader", true); + for(var table : instance.getTables().values()) + { + if(!table.getName().startsWith("edi_") || table.getName().equals(PreviewDownloadBackend.TABLE)) + { + continue; + } + viewer.withPermissions("edi." + table.getName().substring(4) + ".read"); + for(var field : table.getFields().values()) + { + if(field.getSupplementalMetaData("edi.classification") instanceof EdiClassifications classified && classified.getClassification().equals("PHI")) + { + field.withFieldSecurityLock(new com.kingsrook.qqq.backend.core.model.metadata.security.FieldSecurityLock().withSecurityKeyType("synthetic_phi_access")); + } + } + } + try + { + QContext.setQSession(viewer); + for(var table : instance.getTables().values()) + { + if(table.getName().startsWith("edi_") && !table.getName().equals(PreviewDownloadBackend.TABLE)) + { + response(new QueryAction().execute(new QueryInput(table.getName()).withFilter(new QQueryFilter()).withShouldFetchHeavyFields(true)), null, "HIDDEN export " + table.getName()); + } + } + for(var widget : instance.getWidgets().values()) + { + var join = EdiTables.joins().stream().filter(j -> j.getName().equals(widget.getName())).findFirst(); + var params = new HashMap(); + if(join.isPresent()) + { + var parents = rows(join.get().getLeftTable()); + assertThat(parents).as(join.get().getLeftTable()).isNotEmpty(); + params.put("tableName", join.get().getLeftTable()); + params.put("id", parents.getLast().getValueString("id")); + } + response(new RenderWidgetAction().execute(new RenderWidgetInput().withWidgetMetaData(widget).withUrlParams(params)), null, "HIDDEN " + widget.getName()); + } + } + finally + { + QContext.setQSession(fullSession); + instance.setEnforcePermissionsInCoreActions(enforcement); + } + } + + /******************************************************************************* + ** Exercise standard Review and Result paths with serialized outputs and summaries. + *******************************************************************************/ + private RunBackendStepOutput process(String name, QRecord record) throws Exception + { + var input = new RunBackendStepInput().withProcessName(name).withRecords(List.of(record)); + var review = new EdiProcesses.Review(); + var preview = new RunBackendStepOutput(); + review.runOnePage(input, preview); + response(Map.of("records", preview.getRecords(), "values", preview.getValues()), null, "Review records"); + response(review.getProcessSummary(preview, false), null, "Review summary"); + var execute = new EdiProcesses.Execute(); + var output = new RunBackendStepOutput(); + execute.runOnePage(input, output); + response(Map.of("records", output.getRecords(), "values", output.getValues()), null, "Result records"); + response(execute.getProcessSummary(output, true), null, "Result summary"); + return (output); + } + + /******************************************************************************* + ** Actual QueryAction rows, including encrypted BLOBs. + *******************************************************************************/ + private static List rows(String table) throws Exception + { + return (new QueryAction().execute(new QueryInput(table).withFilter(new QQueryFilter()).withShouldFetchHeavyFields(true)).getRecords()); + } + + /******************************************************************************* + ** Capture complete alert payloads, not merely alert type names. + *******************************************************************************/ + public static class CapturedAlerts implements com.kingsrook.qqq.backend.core.alerts.AlertSinkInterface + { + static final List outputs = new ArrayList<>(); + + /******************************************************************************* + ** Retain every serialized signal for the substring scanner. + *******************************************************************************/ + public void raise(com.kingsrook.qqq.backend.core.alerts.AlertSignal signal) + { + outputs.add(JsonUtils.toJson(signal)); + } + + /******************************************************************************* + ** Resolutions are egress too. + *******************************************************************************/ + public void resolve(com.kingsrook.qqq.backend.core.alerts.AlertResolution resolution) + { + outputs.add(JsonUtils.toJson(resolution)); + } + + /******************************************************************************* + ** Heartbeats contain only the operational key and time. + *******************************************************************************/ + public void heartbeat(String key, Instant at) + { + outputs.add(key + at); + } + } + + /******************************************************************************* + ** In-memory Log4j appender includes rendered throwable stacks in the scan. + *******************************************************************************/ + private static class CapturedLogs extends AbstractAppender + { + private final List outputs = new java.util.concurrent.CopyOnWriteArrayList<>(); + + /******************************************************************************* + ** No filesystem output or production logging reconfiguration. + *******************************************************************************/ + private CapturedLogs() + { + super("phi-egress", null, PatternLayout.newBuilder().withPattern("%c %m%ex").build(), false, null); + } + + /******************************************************************************* + ** Capture the exact formatted event including parameter values and exceptions. + *******************************************************************************/ + @Override + public void append(LogEvent event) + { + outputs.add(new String(getLayout().toByteArray(event), StandardCharsets.UTF_8)); + } + } +} diff --git a/src/test/java/com/kingsrook/qbits/edi/demo/EdiDemoServer.java b/src/test/java/com/kingsrook/qbits/edi/demo/EdiDemoServer.java index f33200b..09eb495 100644 --- a/src/test/java/com/kingsrook/qbits/edi/demo/EdiDemoServer.java +++ b/src/test/java/com/kingsrook/qbits/edi/demo/EdiDemoServer.java @@ -87,6 +87,7 @@ public class EdiDemoServer extends AbstractQQQApplication implements AutoCloseab EdiRuntime.current().config().withSecretsResolver(name -> secrets.getOrDefault(name, Base64.getEncoder().encodeToString(new byte[32]))); RetailSupport.tables(); LogisticsSupport.tables(); + com.kingsrook.qbits.edi.HealthcareSupport.tables(); instance.setHomeAppName("edi"); for(String table : List.of("sample_order", "sample_shipment", "sample_invoice", "sample_inventory", "sample_wms_shipment", "sample_load")) { @@ -245,10 +246,34 @@ public class EdiDemoServer extends AbstractQQQApplication implements AutoCloseab } logistics(local, agreements.get(1)); seedAs2(local); + healthcare(); return (null); }); } + /******************************************************************************* + ** Synthetic clearinghouse claims, remittance and batch eligibility exchange. + *******************************************************************************/ + private void healthcare() throws Exception + { + var agreement = com.kingsrook.qbits.edi.HealthcareSupport.configure(); + int control = 500; + for(String key : List.of("837P", "837I", "835")) + { + com.kingsrook.qbits.edi.HealthcareSupport.receive(key, ++control, com.kingsrook.qbits.edi.HealthcareSupport.body(key, control)); + } + com.kingsrook.qbits.edi.HealthcareSupport.receive("837P", ++control, com.kingsrook.qbits.edi.HealthcareSupport.body("837P", control).replace("*150***", "*151***")); + com.kingsrook.qbits.edi.HealthcareSupport.receive("837P", ++control, com.kingsrook.qbits.edi.HealthcareSupport.body("837P", control).replace("SBR*P*18", "SBR*P*19")); + QRecord source = SystemWrites.insert("sample_eligibility_request", new QRecord().withValue("trace", "SYNTRACE506").withValue("memberId", "SYN000123").withValue("patientName", "Testpatient Alpha")); + com.kingsrook.qbits.edi.flow.OutboundEnqueue.enqueue("sample_eligibility_request", source, "MANUAL", null); + new OutboundWorker().runOnce(); + com.kingsrook.qbits.edi.HealthcareSupport.receive("271", ++control, com.kingsrook.qbits.edi.HealthcareSupport.body("271", control)); + while(new SendWorker().runOnce() > 0) + { + // Drain only the configured in-memory outboxes. + } + } + /******************************************************************************* ** Warehouse and logistics traffic: Harbor Logistics (3PL) sends 940s and receives 945s, ** and Ridgeline Freight (carrier) receives 204 tenders and sends 214 statuses. diff --git a/src/test/java/com/kingsrook/qbits/edi/demo/EdiDemoServerTest.java b/src/test/java/com/kingsrook/qbits/edi/demo/EdiDemoServerTest.java index 072b19b..e5eb009 100644 --- a/src/test/java/com/kingsrook/qbits/edi/demo/EdiDemoServerTest.java +++ b/src/test/java/com/kingsrook/qbits/edi/demo/EdiDemoServerTest.java @@ -86,7 +86,7 @@ class EdiDemoServerTest demo.seed(); demo.seed(); assertThat(demo.defineQInstance().getHomeAppName()).isEqualTo("edi"); - assertThat(SystemWrites.call(() -> EdiRecords.query("edi_partner", new QQueryFilter()))).hasSize(5); + assertThat(SystemWrites.call(() -> EdiRecords.query("edi_partner", new QQueryFilter()))).hasSize(6); assertThat(SystemWrites.call(() -> EdiRecords.query("edi_certificate", new QQueryFilter()))).hasSize(5); assertThat(SystemWrites.call(() -> EdiRecords.query("edi_inbox", new QQueryFilter()))).hasSizeGreaterThanOrEqualTo(40); assertThat(SystemWrites.call(() -> EdiRecords.query("edi_transaction", new QQueryFilter()))).anyMatch(row -> "OUTBOUND".equals(row.getValueString("direction"))); @@ -94,7 +94,7 @@ class EdiDemoServerTest assertThat(SystemWrites.call(() -> EdiRecords.query("edi_functional_group", new QQueryFilter()))).anyMatch(row -> row.getValueInstant("ackReceivedAt") != null); assertThat(SystemWrites.call(() -> EdiRecords.query("edi_exception", new QQueryFilter()))).anyMatch(row -> "OVERDUE_ACK".equals(row.getValueString("type"))); var partners = SystemWrites.call(() -> EdiRecords.query("edi_partner", new QQueryFilter())); - assertThat(partners).extracting(row -> row.getValueString("code")).containsExactlyInAnyOrder("NORTHWIND", "HARBOR", "SUMMIT", "RIDGELINE", "PACIFIC"); + assertThat(partners).extracting(row -> row.getValueString("code")).containsExactlyInAnyOrder("NORTHWIND", "HARBOR", "SUMMIT", "RIDGELINE", "PACIFIC", "CEDAR"); assertThat(partners).allSatisfy(row -> assertThat(row.getValueString("contactEmail")).endsWith("@example.invalid")); assertThat(SystemWrites.call(() -> EdiRecords.query("sample_wms_order", new QQueryFilter()))).hasSize(3); assertThat(SystemWrites.call(() -> EdiRecords.query("sample_shipment_status", new QQueryFilter()))).hasSize(3); diff --git a/src/test/resources/edi/maps/270-005010X279A1-sample.json b/src/test/resources/edi/maps/270-005010X279A1-sample.json new file mode 100644 index 0000000..3efe917 --- /dev/null +++ b/src/test/resources/edi/maps/270-005010X279A1-sample.json @@ -0,0 +1,254 @@ +{ + "map": { + "name": "Synthetic 270 outbound", + "direction": "OUTBOUND", + "transactionSet": "270", + "version": "005010X279A1", + "tableName": "sample_eligibility_request", + "revision": 1, + "status": "ACTIVE", + "businessKeyType": "HEALTHCARE", + "businessKeyPath": "TRN02", + "standard": "X12", + "onMatch": "UPDATE" + }, + "rules": [ + { + "sequence": 2, + "ediPath": "BHT01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"0022\"}" + }, + { + "sequence": 3, + "ediPath": "BHT02", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"13\"}" + }, + { + "sequence": 4, + "ediPath": "BHT03", + "qqqPath": "trace" + }, + { + "sequence": 5, + "ediPath": "BHT04", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"20261015\"}" + }, + { + "sequence": 6, + "ediPath": "BHT05", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1200\"}" + }, + { + "sequence": 7, + "ediPath": "2000A/HL01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 8, + "ediPath": "2000A/HL02", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"\"}" + }, + { + "sequence": 9, + "ediPath": "2000A/HL03", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"20\"}" + }, + { + "sequence": 10, + "ediPath": "2000A/HL04", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 11, + "ediPath": "2000A/2100A/NM101", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"PR\"}" + }, + { + "sequence": 12, + "ediPath": "2000A/2100A/NM102", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"2\"}" + }, + { + "sequence": 13, + "ediPath": "2000A/2100A/NM103", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"Synthetic PR\"}" + }, + { + "sequence": 14, + "ediPath": "2000A/2100A/NM108", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"PI\"}" + }, + { + "sequence": 15, + "ediPath": "2000A/2100A/NM109", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"SYNPLAN\"}" + }, + { + "sequence": 16, + "ediPath": "2000B/HL01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"2\"}" + }, + { + "sequence": 17, + "ediPath": "2000B/HL02", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 18, + "ediPath": "2000B/HL03", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"21\"}" + }, + { + "sequence": 19, + "ediPath": "2000B/HL04", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 20, + "ediPath": "2000B/2100B/NM101", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1P\"}" + }, + { + "sequence": 21, + "ediPath": "2000B/2100B/NM102", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"2\"}" + }, + { + "sequence": 22, + "ediPath": "2000B/2100B/NM103", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"Synthetic 1P\"}" + }, + { + "sequence": 23, + "ediPath": "2000B/2100B/NM108", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"XX\"}" + }, + { + "sequence": 24, + "ediPath": "2000B/2100B/NM109", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1234567893\"}" + }, + { + "sequence": 25, + "ediPath": "2000C/HL01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"3\"}" + }, + { + "sequence": 26, + "ediPath": "2000C/HL02", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"2\"}" + }, + { + "sequence": 27, + "ediPath": "2000C/HL03", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"22\"}" + }, + { + "sequence": 28, + "ediPath": "2000C/HL04", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"0\"}" + }, + { + "sequence": 29, + "ediPath": "2000C/TRN01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 30, + "ediPath": "2000C/TRN02", + "qqqPath": "trace" + }, + { + "sequence": 31, + "ediPath": "2000C/2100C/NM101", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"IL\"}" + }, + { + "sequence": 32, + "ediPath": "2000C/2100C/NM102", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 33, + "ediPath": "2000C/2100C/NM103", + "qqqPath": "patientName" + }, + { + "sequence": 34, + "ediPath": "2000C/2100C/NM108", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"MI\"}" + }, + { + "sequence": 35, + "ediPath": "2000C/2100C/NM109", + "qqqPath": "memberId" + }, + { + "sequence": 36, + "ediPath": "2000C/2100C/2110C/EQ01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"30\"}" + } + ] +} diff --git a/src/test/resources/edi/maps/271-005010X279A1-sample.json b/src/test/resources/edi/maps/271-005010X279A1-sample.json new file mode 100644 index 0000000..c6a5327 --- /dev/null +++ b/src/test/resources/edi/maps/271-005010X279A1-sample.json @@ -0,0 +1,43 @@ +{ + "map": { + "name": "Synthetic 271 inbound", + "direction": "INBOUND", + "transactionSet": "271", + "version": "005010X279A1", + "tableName": "sample_eligibility_response", + "revision": 1, + "status": "ACTIVE", + "businessKeyType": "HEALTHCARE", + "businessKeyPath": "TRN02", + "standard": "X12", + "onMatch": "UPDATE", + "matchKeyFields": "trace" + }, + "rules": [ + { + "sequence": 1, + "ediPath": "TRN02", + "qqqPath": "trace" + }, + { + "sequence": 2, + "ediPath": "2100C/NM109", + "qqqPath": "memberId" + }, + { + "sequence": 3, + "ediPath": "2100C/NM103", + "qqqPath": "patientName" + }, + { + "sequence": 4, + "ediPath": "EB01", + "qqqPath": "benefit" + }, + { + "sequence": 5, + "ediPath": "EB03", + "qqqPath": "serviceType" + } + ] +} diff --git a/src/test/resources/edi/maps/276-005010X212-sample.json b/src/test/resources/edi/maps/276-005010X212-sample.json new file mode 100644 index 0000000..f3bf280 --- /dev/null +++ b/src/test/resources/edi/maps/276-005010X212-sample.json @@ -0,0 +1,259 @@ +{ + "map": { + "name": "Synthetic 276 outbound", + "direction": "OUTBOUND", + "transactionSet": "276", + "version": "005010X212", + "tableName": "sample_claim_status", + "revision": 1, + "status": "ACTIVE", + "businessKeyType": "HEALTHCARE", + "businessKeyPath": "TRN02", + "standard": "X12", + "onMatch": "UPDATE" + }, + "rules": [ + { + "sequence": 2, + "ediPath": "BHT01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"0010\"}" + }, + { + "sequence": 3, + "ediPath": "BHT02", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"13\"}" + }, + { + "sequence": 4, + "ediPath": "BHT03", + "qqqPath": "trace" + }, + { + "sequence": 5, + "ediPath": "BHT04", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"20261015\"}" + }, + { + "sequence": 6, + "ediPath": "BHT05", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1200\"}" + }, + { + "sequence": 7, + "ediPath": "2000A/HL01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 8, + "ediPath": "2000A/HL02", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"\"}" + }, + { + "sequence": 9, + "ediPath": "2000A/HL03", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"20\"}" + }, + { + "sequence": 10, + "ediPath": "2000A/HL04", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 11, + "ediPath": "2000A/2100A/NM101", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"PR\"}" + }, + { + "sequence": 12, + "ediPath": "2000A/2100A/NM102", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"2\"}" + }, + { + "sequence": 13, + "ediPath": "2000A/2100A/NM103", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"Synthetic PR\"}" + }, + { + "sequence": 14, + "ediPath": "2000A/2100A/NM108", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"PI\"}" + }, + { + "sequence": 15, + "ediPath": "2000A/2100A/NM109", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"SYNPLAN\"}" + }, + { + "sequence": 16, + "ediPath": "2000B/HL01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"2\"}" + }, + { + "sequence": 17, + "ediPath": "2000B/HL02", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 18, + "ediPath": "2000B/HL03", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"21\"}" + }, + { + "sequence": 19, + "ediPath": "2000B/HL04", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 20, + "ediPath": "2000B/2100B/NM101", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1P\"}" + }, + { + "sequence": 21, + "ediPath": "2000B/2100B/NM102", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"2\"}" + }, + { + "sequence": 22, + "ediPath": "2000B/2100B/NM103", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"Synthetic 1P\"}" + }, + { + "sequence": 23, + "ediPath": "2000B/2100B/NM108", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"XX\"}" + }, + { + "sequence": 24, + "ediPath": "2000B/2100B/NM109", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1234567893\"}" + }, + { + "sequence": 25, + "ediPath": "2000C/HL01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"3\"}" + }, + { + "sequence": 26, + "ediPath": "2000C/HL02", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"2\"}" + }, + { + "sequence": 27, + "ediPath": "2000C/HL03", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"22\"}" + }, + { + "sequence": 28, + "ediPath": "2000C/HL04", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"0\"}" + }, + { + "sequence": 29, + "ediPath": "2000C/2100C/NM101", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"IL\"}" + }, + { + "sequence": 30, + "ediPath": "2000C/2100C/NM102", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 31, + "ediPath": "2000C/2100C/NM103", + "qqqPath": "patientName" + }, + { + "sequence": 32, + "ediPath": "2000C/2100C/NM108", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"MI\"}" + }, + { + "sequence": 33, + "ediPath": "2000C/2100C/NM109", + "qqqPath": "memberId" + }, + { + "sequence": 34, + "ediPath": "2000C/2100C/2200C/TRN01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"1\"}" + }, + { + "sequence": 35, + "ediPath": "2000C/2100C/2200C/TRN02", + "qqqPath": "trace" + }, + { + "sequence": 36, + "ediPath": "2000C/2100C/2200C/REF01", + "qqqPath": null, + "transform": "CONSTANT", + "transformArgs": "{\"value\": \"EJ\"}" + }, + { + "sequence": 37, + "ediPath": "2000C/2100C/2200C/REF02", + "qqqPath": "claimNumber" + } + ] +} diff --git a/src/test/resources/edi/maps/277-005010X212-sample.json b/src/test/resources/edi/maps/277-005010X212-sample.json new file mode 100644 index 0000000..f6b3417 --- /dev/null +++ b/src/test/resources/edi/maps/277-005010X212-sample.json @@ -0,0 +1,43 @@ +{ + "map": { + "name": "Synthetic 277 inbound", + "direction": "INBOUND", + "transactionSet": "277", + "version": "005010X212", + "tableName": "sample_claim_status", + "revision": 1, + "status": "ACTIVE", + "businessKeyType": "HEALTHCARE", + "businessKeyPath": "2200C/TRN02", + "standard": "X12", + "onMatch": "UPDATE", + "matchKeyFields": "trace" + }, + "rules": [ + { + "sequence": 1, + "ediPath": "2200C/TRN02", + "qqqPath": "trace" + }, + { + "sequence": 2, + "ediPath": "2100C/NM109", + "qqqPath": "memberId" + }, + { + "sequence": 3, + "ediPath": "STC01-1", + "qqqPath": "category" + }, + { + "sequence": 4, + "ediPath": "STC01-2", + "qqqPath": "statusCode" + }, + { + "sequence": 5, + "ediPath": "REF[REF01=EJ]/REF02", + "qqqPath": "claimNumber" + } + ] +} diff --git a/src/test/resources/edi/maps/834-005010X220A1-sample.json b/src/test/resources/edi/maps/834-005010X220A1-sample.json new file mode 100644 index 0000000..613122f --- /dev/null +++ b/src/test/resources/edi/maps/834-005010X220A1-sample.json @@ -0,0 +1,45 @@ +{ + "map": { + "name": "Synthetic 834 inbound", + "direction": "INBOUND", + "transactionSet": "834", + "version": "005010X220A1", + "tableName": "sample_enrollment", + "revision": 1, + "status": "ACTIVE", + "businessKeyType": "HEALTHCARE", + "businessKeyPath": "BGN02", + "standard": "X12", + "onMatch": "UPDATE", + "matchKeyFields": "reference" + }, + "rules": [ + { + "sequence": 1, + "ediPath": "BGN02", + "qqqPath": "reference" + }, + { + "sequence": 2, + "ediPath": "2000[*]/REF[REF01=0F]/REF02", + "qqqPath": "members.memberId" + }, + { + "sequence": 3, + "ediPath": "2000[*]/2100A/NM103", + "qqqPath": "members.patientName" + }, + { + "sequence": 4, + "ediPath": "2000[*]/2100A/DMG02", + "qqqPath": "members.birthDate", + "transform": "DATE", + "condition": "present(2100A/DMG02)" + }, + { + "sequence": 5, + "ediPath": "2000[*]/2300/HD03", + "qqqPath": "members.coverage" + } + ] +} diff --git a/src/test/resources/edi/maps/835-005010X221A1-sample.json b/src/test/resources/edi/maps/835-005010X221A1-sample.json new file mode 100644 index 0000000..29f0a16 --- /dev/null +++ b/src/test/resources/edi/maps/835-005010X221A1-sample.json @@ -0,0 +1,59 @@ +{ + "map": { + "name": "Synthetic 835 inbound", + "direction": "INBOUND", + "transactionSet": "835", + "version": "005010X221A1", + "tableName": "sample_remittance", + "revision": 1, + "status": "ACTIVE", + "businessKeyType": "HEALTHCARE", + "businessKeyPath": "TRN02", + "standard": "X12", + "onMatch": "UPDATE", + "matchKeyFields": "trace" + }, + "rules": [ + { + "sequence": 1, + "ediPath": "TRN02", + "qqqPath": "trace" + }, + { + "sequence": 2, + "ediPath": "BPR02", + "qqqPath": "payment", + "transform": "DECIMAL", + "condition": "present(BPR02)" + }, + { + "sequence": 3, + "ediPath": "2100[*]/CLP01", + "qqqPath": "claims.claimNumber" + }, + { + "sequence": 4, + "ediPath": "2100[*]/CLP03", + "qqqPath": "claims.charge", + "transform": "DECIMAL", + "condition": "present(CLP03)" + }, + { + "sequence": 5, + "ediPath": "2100[*]/CLP04", + "qqqPath": "claims.paid", + "transform": "DECIMAL", + "condition": "present(CLP04)" + }, + { + "sequence": 6, + "ediPath": "2100[*]/NM1[NM101=QC]/NM103", + "qqqPath": "claims.patientName" + }, + { + "sequence": 7, + "ediPath": "2100[*]/NM1[NM101=IL]/NM109", + "qqqPath": "claims.memberId" + } + ] +} diff --git a/src/test/resources/edi/maps/837I-005010X223A2-sample.json b/src/test/resources/edi/maps/837I-005010X223A2-sample.json new file mode 100644 index 0000000..4e9053f --- /dev/null +++ b/src/test/resources/edi/maps/837I-005010X223A2-sample.json @@ -0,0 +1,68 @@ +{ + "map": { + "name": "Synthetic 837I inbound", + "direction": "INBOUND", + "transactionSet": "837", + "version": "005010X223A2", + "tableName": "sample_claim", + "revision": 1, + "status": "ACTIVE", + "businessKeyType": "HEALTHCARE", + "businessKeyPath": "CLM01", + "standard": "X12", + "onMatch": "UPDATE", + "matchKeyFields": "claimNumber" + }, + "rules": [ + { + "sequence": 1, + "ediPath": "CLM01", + "qqqPath": "claimNumber" + }, + { + "sequence": 2, + "ediPath": "2010BA/NM109", + "qqqPath": "memberId" + }, + { + "sequence": 3, + "ediPath": "2010BA/NM103", + "qqqPath": "patientName" + }, + { + "sequence": 4, + "ediPath": "CLM02", + "qqqPath": "totalCharge", + "transform": "DECIMAL", + "condition": "present(CLM02)" + }, + { + "sequence": 5, + "ediPath": "HI01-2", + "qqqPath": "diagnosis" + }, + { + "sequence": 6, + "ediPath": "2400[*]/LX01", + "qqqPath": "lines.lineNumber" + }, + { + "sequence": 7, + "ediPath": "2400[*]/SV202-2", + "qqqPath": "lines.procedureCode" + }, + { + "sequence": 8, + "ediPath": "2400[*]/SV203", + "qqqPath": "lines.charge", + "transform": "DECIMAL", + "condition": "present(SV203)" + }, + { + "sequence": 9, + "ediPath": "2010CA/NM103", + "qqqPath": "patientName", + "condition": "present(2010CA/NM103)" + } + ] +} diff --git a/src/test/resources/edi/maps/837P-005010X222A1-sample.json b/src/test/resources/edi/maps/837P-005010X222A1-sample.json new file mode 100644 index 0000000..80755d3 --- /dev/null +++ b/src/test/resources/edi/maps/837P-005010X222A1-sample.json @@ -0,0 +1,68 @@ +{ + "map": { + "name": "Synthetic 837P inbound", + "direction": "INBOUND", + "transactionSet": "837", + "version": "005010X222A1", + "tableName": "sample_claim", + "revision": 1, + "status": "ACTIVE", + "businessKeyType": "HEALTHCARE", + "businessKeyPath": "CLM01", + "standard": "X12", + "onMatch": "UPDATE", + "matchKeyFields": "claimNumber" + }, + "rules": [ + { + "sequence": 1, + "ediPath": "CLM01", + "qqqPath": "claimNumber" + }, + { + "sequence": 2, + "ediPath": "2010BA/NM109", + "qqqPath": "memberId" + }, + { + "sequence": 3, + "ediPath": "2010BA/NM103", + "qqqPath": "patientName" + }, + { + "sequence": 4, + "ediPath": "CLM02", + "qqqPath": "totalCharge", + "transform": "DECIMAL", + "condition": "present(CLM02)" + }, + { + "sequence": 5, + "ediPath": "HI01-2", + "qqqPath": "diagnosis" + }, + { + "sequence": 6, + "ediPath": "2400[*]/LX01", + "qqqPath": "lines.lineNumber" + }, + { + "sequence": 7, + "ediPath": "2400[*]/SV101-2", + "qqqPath": "lines.procedureCode" + }, + { + "sequence": 8, + "ediPath": "2400[*]/SV102", + "qqqPath": "lines.charge", + "transform": "DECIMAL", + "condition": "present(SV102)" + }, + { + "sequence": 9, + "ediPath": "2010CA/NM103", + "qqqPath": "patientName", + "condition": "present(2010CA/NM103)" + } + ] +}