feat: phase e - HIPAA healthcare documents, SNIP 3-5, PHI handling, canary tests #11
No files matched your search
+8
-1
@@ -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).
|
||||
@@ -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
|
||||
|
||||
+16
-1
@@ -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.
|
||||
+45
-8
@@ -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-<random>` 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.
|
||||
@@ -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<com.kingsrook.qqq.backend.core.model.session.QSession> serviceSessionSupplier = null;
|
||||
private java.time.Clock clock = java.time.Clock.systemUTC();
|
||||
|
||||
@@ -53,7 +53,7 @@ public final class EdiRuntime
|
||||
private static final Map<QInstance, EdiRuntime> 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())
|
||||
|
||||
@@ -57,7 +57,7 @@ final class FunctionalAckBuilder
|
||||
for(X12TransactionSet transaction : group.transactions())
|
||||
{
|
||||
List<ValidationFinding> 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)
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -312,7 +312,7 @@ public class EdiHome extends AbstractWidgetRenderer
|
||||
List<Map<String, Object>> 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())
|
||||
|
||||
@@ -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<ValidationFinding> 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())
|
||||
|
||||
@@ -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"), () ->
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
*******************************************************************************/
|
||||
public class BuiltInGuideLoader
|
||||
{
|
||||
public static final List<String> 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<String> 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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<String> 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<String> 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<String> codes, String fixedValue, Integer repeatMax, String condition)
|
||||
{
|
||||
this(position, componentPosition, elementId, dataType, minLength, maxLength, usage, codes, fixedValue, repeatMax, condition, null, null);
|
||||
}
|
||||
}
|
||||
@@ -59,8 +59,10 @@ public class GuideRepository
|
||||
for(QRecord element : rows("edi_guide_element", "guideSegmentId", segmentId))
|
||||
{
|
||||
List<String> 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<GuideRule> 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);
|
||||
}
|
||||
|
||||
@@ -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<ValidationFinding> validate(X12TransactionSet transaction, Guide guide, boolean containsPhi)
|
||||
{
|
||||
State state = new State(transaction.segments(), containsPhi);
|
||||
List<Node> roots = new ArrayList<>();
|
||||
Map<String, Node> nodes = new LinkedHashMap<>();
|
||||
for(GuideSegment segment : guide.segments())
|
||||
{
|
||||
List<Node> 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<Node> 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<String, List<List<X12Segment>>> loops(X12TransactionSet transaction, Guide guide)
|
||||
{
|
||||
State state = new State(transaction.segments(), true);
|
||||
List<Node> 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<Node> tree(Guide guide)
|
||||
{
|
||||
List<Node> roots = new ArrayList<>();
|
||||
Map<String, Node> nodes = new LinkedHashMap<>();
|
||||
Map<String, Node> latest = new LinkedHashMap<>();
|
||||
for(GuideSegment segment : guide.segments())
|
||||
{
|
||||
List<Node> destination = roots;
|
||||
if(segment.loopId() != null && !segment.loopId().isBlank())
|
||||
{
|
||||
Node loop = nodes.get(segment.loopId());
|
||||
Node parent = latest.get(segment.parentLoopId());
|
||||
List<Node> 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<X12Segment> 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<X12Segment> visible = new ArrayList<>(scope);
|
||||
for(List<X12Segment> 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.
|
||||
*******************************************************************************/
|
||||
|
||||
@@ -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<ValidationFinding> validate(X12TransactionSet transaction, Guide guide, Map<String, List<List<X12Segment>>> loops)
|
||||
{
|
||||
List<ValidationFinding> findings = new ArrayList<>();
|
||||
List<X12Segment> segments = transaction.segments();
|
||||
if("837".equals(guide.transactionSet()))
|
||||
{
|
||||
for(List<X12Segment> 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<X12Segment> segments, List<ValidationFinding> findings)
|
||||
{
|
||||
for(List<X12Segment> 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<List<X12Segment>> blocks(List<X12Segment> segments, String anchor)
|
||||
{
|
||||
List<List<X12Segment>> blocks = new ArrayList<>();
|
||||
List<X12Segment> 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<X12Segment> 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));
|
||||
}
|
||||
}
|
||||
@@ -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<ProcessSummaryLineInterface> 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"));
|
||||
|
||||
@@ -62,7 +62,10 @@ public class OutboundMapper
|
||||
throw new IllegalArgumentException("An outbound map is required");
|
||||
}
|
||||
List<X12Segment> 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<QRecord> 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));
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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<Integer> DEPTH = ThreadLocal.withInitial(() -> 0);
|
||||
private static final Map<Configuration, Boolean> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))));
|
||||
}
|
||||
|
||||
|
||||
@@ -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<QRecord> 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)));
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "QQQ baseline 999 005010X231A1",
|
||||
"name": "QQQ baseline guide 999 005010X231A1",
|
||||
"standard": "X12",
|
||||
"version": "005010X231A1",
|
||||
"transactionSet": "999",
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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<ValidationFinding> 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<QRecord> 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<QRecord> 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<String>();
|
||||
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<QRecord> 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")));
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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<String> 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<QRecord> received = new ArrayList<>();
|
||||
for(int variant = 0; variant < 2; variant++)
|
||||
{
|
||||
List<String> 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<String, String>();
|
||||
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<QRecord> 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<String> 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<String> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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\"}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user