Contract Data Normalization

Overview

Contract Data Normalization

Bushel’s system contains a large amount of data that is sourced from various upstream systems. This data is often normalized and transformed in microservices to ensure that it is consistent and usable across Bushel’s ecosystem. This section includes information on how Bushel normalizes Contract data to ensure that it is consistent and usable across Bushel’s ecosystem.

Contract Type

Incoming contract type and subtype values from external systems are normalized into a standard representation before they are displayed or stored.

How these values are determined from sourceType and sourceSubtype:

  • Both inputs are trimmed before processing.

  • The service first tries to infer a standard ContractType from sourceType using common aliases. If this succeeds, type is set and subtype is taken from sourceSubtype.

  • If sourceType is not standard, it then tries to infer type from sourceSubtype using the same aliases. When this succeeds, subtype is taken from sourceType.

  • If neither field matches a standard alias, the service attempts to infer type from extended terms in sourceType (for example, certain vendor-specific values that clearly imply a sales or purchase type). When this succeeds, subtype is taken from sourceSubtype.

  • If a standard type is found and the resulting subtype is identical to the type name (ignoring case), the subtype is cleared (set to null) to avoid redundant labels.

  • If no standard type can be determined and both inputs are non-null and equal (ignoring case), the shared value is treated as the subtype.

  • If no standard type can be determined and the inputs are different, the non-blank values are combined into a single subtype string joined with ` - ` (for example, "Not - Standard").

The concatType value is then built by joining the normalized type name (if present) and subtype with ` - `, and omitting any blank parts. This label is what the display-format setting uses when rendering contract type in the UI.

Examples:

sourceType sourceSubtype (type, subtype) concatType

"Purchase"

"Basis"

(purchase, "Basis")

"purchase - Basis"

"HTA"

"Sales"

(sales, "HTA") (type inferred from subtype)

"sales - HTA"

"Purchase"

"purchase"

(purchase, null) (subtype cleared because it matches type)

"purchase"

"Not"

"Standard"

(null, "Not - Standard") (no standard type, combined subtype)

"Not - Standard"

null

null

(null, null)

null

Normalization attempts to infer the standard type using both the raw type and subtype fields, including common aliases and selected extended terms. When no match is found, the service preserves the original values by combining the non-blank inputs into a single subtype value, as shown above.

Signature Status

Incoming signature status values (sourceSignatureStatus) from external systems are normalized into a standard SignatureStatus value before they are stored and used for display or filtering.

The normalized value is stored as signatureStatus on the contract and is one of:

  • signed

  • needs_signature

  • no_signature_required

  • null when no mapping applies

Normalization follows these rules:

  • If sourceSignatureStatus is exactly "Signed", it is always mapped to SignatureStatus.signed, even if no tenant configuration is present.

  • Otherwise, if there is no tenant configuration or sourceSignatureStatus is blank, the normalized signatureStatus is null.

  • When a tenant configuration exists, the service reads contractSignatureStatusMappings JSON on the tenant, which must contain a defaultMapping object and may contain a datasourceOverrides array.

  • Each mapping object maps a raw status code (for example, "S", "N") to a display string (for example, "Signed", "Needs Signature"). Supported display strings are:

    • "Signed"SignatureStatus.signed

    • "Needs Signature"SignatureStatus.needs_signature

    • "No Signature Required"SignatureStatus.no_signature_required

Mapping resolution works as follows:

  • If no datasource overrides are defined, defaultMapping is used for all datasources.

  • If datasourceOverrides is present, the service looks for an entry whose datasourceId matches the contract’s datasource. If found and it contains a mappings object, that object is used; otherwise, defaultMapping is used as a fallback.

  • If the mapping object does not contain an entry for the raw sourceSignatureStatus, or the mapped display string is not one of the supported values above, the normalized signatureStatus is null.

The original raw value is always preserved separately as sourceSignatureStatus on the contract entity and can be inspected for debugging or support, but UI and filtering should rely on the normalized signatureStatus field.