Skip to content

Commit

Permalink
DT-1533-Update data validations in booking (#180)
Browse files Browse the repository at this point in the history
  • Loading branch information
palatsangeetha authored Oct 9, 2024
1 parent 85e40cb commit da79b28
Show file tree
Hide file tree
Showing 13 changed files with 0 additions and 118,402 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -104,26 +104,6 @@ public static ActionCheck requestContentChecks(UUID matched, String standardVers
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(NATIONAL_COMMODITY_TYPE_CODES)
);

private static final JsonContentCheck VALIDATE_ALL_BOOKING_UN_LOCATION_CODES = JsonAttribute.allIndividualMatchesMustBeValid(
"Validate all booking UNLocationCodes",
mav -> {
mav.submitAllMatching("shipmentLocations.*.location.UNLocationCode");
mav.submitAllMatching("invoicePayableAt.UNLocationCode");
mav.submitAllMatching("placeOfBLIssue.UNLocationCode");
mav.submitAllMatching("transportPlan.loadLocation.UNLocationCode");
mav.submitAllMatching("transportPlan.dischargeLocation.UNLocationCode");

// Beta-2 only
mav.submitAllMatching("documentParties.shipper.address.UNLocationCode");
mav.submitAllMatching("documentParties.consignee.address.UNLocationCode");
mav.submitAllMatching("documentParties.bookingAgent.address.UNLocationCode");
mav.submitAllMatching("documentParties.serviceContractOwner.address.UNLocationCode");
mav.submitAllMatching("documentParties.carrierBookingOffice.address.UNLocationCode");
mav.submitAllMatching("documentParties.other.*.party.address.UNLocationCode");

},
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(BookingDataSets.UN_LOCODE_DATASET)
);

private static final JsonContentCheck CHECK_EXPECTED_ARRIVAL_POD = JsonAttribute.customValidator(
"Check expected arrival dates are valid",
Expand Down Expand Up @@ -207,27 +187,6 @@ public static ActionCheck requestContentChecks(UUID matched, String standardVers
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(BookingDataSets.REFERENCE_TYPES)
);

private static final JsonContentCheck TLR_TYPE_CODE_VALIDATIONS = JsonAttribute.allIndividualMatchesMustBeValid(
"Validate 'type' in 'taxAndLegalReferences' static data",
mav -> mav.submitAllMatching("documentParties.*.party.taxLegalReferences.*"),
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(BookingDataSets.LTR_TYPE_CODES)
);

private static final JsonContentCheck ISO_EQUIPMENT_CODE_VALIDATION = JsonAttribute.allIndividualMatchesMustBeValid(
"Validate ISO Equipment code",
mav -> {
mav.submitAllMatching("requestedEquipments.*.ISOEquipmentCode");
mav.submitAllMatching("confirmedEquipments.*.ISOEquipmentCode");
},
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(BookingDataSets.ISO_6346_CONTAINER_CODES)
);

private static final JsonContentCheck OUTER_PACKAGING_CODE_IS_VALID = JsonAttribute.allIndividualMatchesMustBeValid(
"Validate that 'packagingCode' is a known code",
mav -> mav.submitAllMatching("requestedEquipments.*.commodities.*.outerPackaging.packageCode"),
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(BookingDataSets.OUTER_PACKAGING_CODE)
);

private static Consumer<MultiAttributeValidator> allDg(Consumer<MultiAttributeValidator.AttributePathBuilder> consumer) {
return mav -> consumer.accept(mav.path("requestedEquipments").all().path("commodities").all().path("outerPackaging").path("dangerousGoods").all());
}
Expand Down Expand Up @@ -337,42 +296,6 @@ private static Set<String> validateDocumentPartyFields(JsonNode documentPartyNod
JsonAttribute.unique("cutOffDateTimeCode")
);

private static final Consumer<MultiAttributeValidator> ALL_CUSTOMS_REFERENCES_TYPE = mav -> {
mav.submitAllMatching("customsReferences.*.type");
mav.submitAllMatching("requestedEquipments.*.customsReferences.*.type");
mav.submitAllMatching("requestedEquipments.*.commodities.*.customsReferences.*.type");
};

private static final JsonRebaseableContentCheck CR_TYPE_CODES_VALIDATIONS = JsonAttribute.allIndividualMatchesMustBeValid(
"Validate 'type' in 'customsReferences' must be valid",
ALL_CUSTOMS_REFERENCES_TYPE,
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(BookingDataSets.CUSTOMS_REFERENCE_RE_REC_TYPE_CODES)
);

private static final JsonContentCheck AMF_MTC_VALIDATIONS = JsonAttribute.allIndividualMatchesMustBeValid(
"Validate 'manifestTypeCode' in 'advanceManifestFilings' static data",
mav -> mav.submitAllMatching("advanceManifestFilings.*.type"),
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(BookingDataSets.AMF_CC_MTC_TYPE_CODES)
);

private static final JsonRebaseableContentCheck COUNTRY_CODE_VALIDATIONS = JsonAttribute.allIndividualMatchesMustBeValid(
"Validate field is a known ISO 3166 alpha 2 code",
mav -> {
mav.submitAllMatching("advancedManifestFilings.*.countryCode");
mav.submitAllMatching("documentParties.*.party.taxLegalReferences.*.countryCode");

// Beta-2 only
mav.submitAllMatching("documentParties.shippers.address.countryCode");
mav.submitAllMatching("documentParties.consignee.address.countryCode");
mav.submitAllMatching("documentParties.endorsee.address.countryCode");
mav.submitAllMatching("documentParties.serviceContractOwner.address.countryCode");
mav.submitAllMatching("documentParties.carrierBookingOffice.address.countryCode");
mav.submitAllMatching("documentParties.other.*.party.address.countryCode");
mav.submitAllMatching("placeOfBLIssue.countryCode");
mav.submitAllMatching("requestedEquipments.*.commodities.*.nationalCommodityCodes.*.countryCode");
},
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(BookingDataSets.ISO_3166_ALPHA2_COUNTRY_CODES)
);

private static final JsonContentCheck VALIDATE_SHIPMENT_LOCATIONS = JsonAttribute.customValidator(
"Validate shipmentLocations",
Expand Down Expand Up @@ -666,28 +589,19 @@ private static void generateScenarioRelatedChecks(List<JsonContentCheck> checks,
private static final List<JsonContentCheck> STATIC_BOOKING_CHECKS = Arrays.asList(
JsonAttribute.mustBeDatasetKeywordIfPresent(JsonPointer.compile("/cargoMovementTypeAtOrigin"), BookingDataSets.CARGO_MOVEMENT_TYPE),
JsonAttribute.mustBeDatasetKeywordIfPresent(JsonPointer.compile("/cargoMovementTypeAtDestination"), BookingDataSets.CARGO_MOVEMENT_TYPE),
JsonAttribute.mustBeDatasetKeywordIfPresent(JsonPointer.compile("/communicationChannelCode"), BookingDataSets.COMMUNICATION_CHANNEL_CODES),
JsonAttribute.mustBeDatasetKeywordIfPresent(JsonPointer.compile("/declaredValueCurrency"), BookingDataSets.ISO_4217_CURRENCY_CODES),
JsonAttribute.mustBeDatasetKeywordIfPresent(JsonPointer.compile("/incoTerms"), BookingDataSets.INCO_TERMS_VALUES),
VALIDATE_ALL_BOOKING_UN_LOCATION_CODES,
CHECK_EXPECTED_DEPARTURE_DATE,
CHECK_EXPECTED_ARRIVAL_POD,
NOR_PLUS_ISO_CODE_IMPLIES_ACTIVE_REEFER,
ISO_EQUIPMENT_CODE_AND_NOR_CHECK,
REFERENCE_TYPE_VALIDATION,
ISO_EQUIPMENT_CODE_VALIDATION,
IS_EXPORT_DECLARATION_REFERENCE_ABSENCE,
IS_IMPORT_DECLARATION_REFERENCE_ABSENCE,
OUTER_PACKAGING_CODE_IS_VALID,
TLR_TYPE_CODE_VALIDATIONS,
DOCUMENT_PARTY_FUNCTIONS_MUST_BE_UNIQUE,
UNIVERSAL_SERVICE_REFERENCE,
VALIDATE_SHIPMENT_CUTOFF_TIME_CODE,
VALIDATE_ALLOWED_SHIPMENT_CUTOFF_CODE,
COUNTRY_CODE_VALIDATIONS,
VALIDATE_SHIPPER_MINIMUM_REQUEST_FIELDS,
VALIDATE_DOCUMENT_PARTY,
CR_TYPE_CODES_VALIDATIONS,
NATIONAL_COMMODITY_TYPE_CODE_VALIDATION,
JsonAttribute.atLeastOneOf(
JsonPointer.compile("/expectedDepartureDate"),
Expand Down Expand Up @@ -733,11 +647,6 @@ private static void generateScenarioRelatedChecks(List<JsonContentCheck> checks,
return Set.of();
}
),
JsonAttribute.allIndividualMatchesMustBeValid(
"The 'imoClass' values must be from dataset",
allDg(dg -> dg.path("imoClass").submitPath()),
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(BookingDataSets.DG_IMO_CLASSES)
),
JsonAttribute.allIndividualMatchesMustBeValid(
"The 'segregationGroups' values must be from dataset",
allDg(dg -> dg.path("segregationGroups").all().submitPath()),
Expand All @@ -752,11 +661,6 @@ private static void generateScenarioRelatedChecks(List<JsonContentCheck> checks,
JsonPointer.compile("/declaredValue"),
JsonPointer.compile("/declaredValueCurrency")
),
JsonAttribute.allIndividualMatchesMustBeValid(
"The charges 'currencyCode' values must be from dataset",
mav -> mav.submitAllMatching("charges.*.currencyCode"),
JsonAttribute.matchedMustBeDatasetKeywordIfPresent(BookingDataSets.ISO_4217_CURRENCY_CODES)
),

JsonAttribute.allIndividualMatchesMustBeValid(
"The charges currency amount must not exceed more than 2 decimal points",
Expand All @@ -773,7 +677,6 @@ private static void generateScenarioRelatedChecks(List<JsonContentCheck> checks,
private static final List<JsonContentCheck> RESPONSE_ONLY_CHECKS = Arrays.asList(
CHECK_ABSENCE_OF_CONFIRMED_FIELDS,
ADVANCED_MANIFEST_FILING_CODES_UNIQUE,
AMF_MTC_VALIDATIONS,
SHIPMENT_CUTOFF_TIMES_UNIQUE,
CHECK_CONFIRMED_BOOKING_FIELDS,
VALIDATE_SHIPMENT_LOCATIONS,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,92 +1,20 @@
package org.dcsa.conformance.standards.booking.checks;

import com.opencsv.CSVReader;
import lombok.SneakyThrows;
import org.dcsa.conformance.core.check.KeywordDataset;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashSet;

public class BookingDataSets {
public static final KeywordDataset UN_LOCODE_DATASET = KeywordDataset.lazyLoaded(BookingDataSets::loadUNLocationCodeDataset);

public static final KeywordDataset CARGO_MOVEMENT_TYPE = KeywordDataset.staticDataset("FCL", "LCL");

public static final KeywordDataset COMMUNICATION_CHANNEL_CODES = KeywordDataset.staticDataset("EI", "EM", "AO");

public static final KeywordDataset INCO_TERMS_VALUES = KeywordDataset.staticDataset("EXW", "FCA", "FAS", "FOB", "CFR", "CIF", "CPT", "CIP", "DAP", "DPU", "DDP");

public static final KeywordDataset NATIONAL_COMMODITY_TYPE_CODES = KeywordDataset.staticDataset( "NCM", "HTS", "Schedule B", "TARIC", "CN", "CUS" );

public static final KeywordDataset CUTOFF_DATE_TIME_CODES = KeywordDataset.staticDataset("DCO", "VCO", "FCO", "LCO", "ECP", "EFC");

public static final KeywordDataset AMF_CC_MTC_TYPE_CODES = KeywordDataset.fromVersionedCSV(BookingDataSets.class, "/standards/booking/datasets/advancemanifestfilings-v%s.csv", "Advance Manifest Filing Type Code");

public static final KeywordDataset ISO_4217_CURRENCY_CODES = KeywordDataset.fromCSV(BookingDataSets.class, "/standards/booking/datasets/currency-codes-iso-4217.csv", "CurrencyCode");

public static final KeywordDataset REFERENCE_TYPES = KeywordDataset.fromCSV(BookingDataSets.class, "/standards/booking/datasets/general-reference-types.csv", "General Reference Type Code");

public static final KeywordDataset ISO_6346_CONTAINER_CODES = KeywordDataset.fromCSV(BookingDataSets.class, "/standards/booking/datasets/iso-6346-container-codes.csv", "code");

public static final KeywordDataset OUTER_PACKAGING_CODE = KeywordDataset.fromCSV(BookingDataSets.class, "/standards/booking/datasets/rec21_Rev12e_Annex-V-VI_2021.csv", "Code");

public static final KeywordDataset DG_IMO_CLASSES = KeywordDataset.fromCSV(BookingDataSets.class, "/standards/booking/datasets/imoclasses.csv");

public static final KeywordDataset DG_SEGREGATION_GROUPS = KeywordDataset.fromCSV(BookingDataSets.class, "/standards/booking/datasets/segregationgroups.csv");

public static final KeywordDataset INHALATION_ZONE_CODE = KeywordDataset.staticDataset("A", "B", "C", "D");

public static final KeywordDataset LTR_TYPE_CODES = KeywordDataset.fromVersionedCSV(BookingDataSets.class, "/standards/booking/datasets/taxandlegalreferences-v%s.csv", "/", "Tax and Legal Reference Country Code", "Tax and Legal Reference Type Code");

public static final KeywordDataset ISO_3166_ALPHA2_COUNTRY_CODES = KeywordDataset.fromCSV(BookingDataSets.class, "/standards/booking/datasets/country-codes-iso3166-alpha2.csv", "Code");

public static final KeywordDataset CUSTOMS_REFERENCE_RE_REC_TYPE_CODES = KeywordDataset.fromVersionedCSV(BookingDataSets.class, "/standards/booking/datasets/customsreferences-v%s.csv", "Customs Reference Type Code");

@SneakyThrows
private static KeywordDataset loadUNLocationCodeDataset() {
var validCodes = new HashSet<>();

for (String file : new String[] {
// No clue why they decided to split the CSV version is split across 3 files
// Combined these files takes about ~0.5 seconds to parse combined. We rely on KeywordDataset.lazyLoaded
// plus re-use of the UN_LOCODE_DATASET to ensure it is only loaded once per report/run.
"2023-1 UNLOCODE CodeListPart1.csv",
"2023-1 UNLOCODE CodeListPart2.csv",
"2023-1 UNLOCODE CodeListPart3.csv",
}) {
var stream = BookingDataSets.class.getResourceAsStream("/standards/booking/datasets/" + file);
if (stream == null) {
throw new IllegalStateException("Missing resource: /standards/booking/datasets/" + file);
}
var builder = new StringBuilder(5);
try (stream) {
try (var csvReader = new CSVReader(new BufferedReader(new InputStreamReader(stream, StandardCharsets.ISO_8859_1)))) {
String[] line;
while ((line = csvReader.readNext()) != null) {
if (line[1].length() != 2) {
throw new IllegalStateException("The CSV file " + file + " does not seem to have the expected format");
}
if (line[2].isBlank()) {
continue;
}
if (line[2].length() != 3) {
throw new IllegalStateException("The CSV file " + file + " does not seem to have the expected format");
}
builder.setLength(0);
var code = builder.append(line[1]).append(line[2]).toString();
if (!code.equals(code.toUpperCase())) {
throw new IllegalStateException("The CSV file " + file + " does not seem to have the expected format");
}
validCodes.add(code);
}
}
}
}

return Collections.unmodifiableSet(validCodes)::contains;
}

}
Loading

0 comments on commit da79b28

Please sign in to comment.