Compliance - Validators
Location: api/nextango/apps/compliance/services/permit_validators.py, api/nextango/apps/compliance/cannabis/services/permit_validators.py, api/nextango/apps/compliance/cannabis/services/credential_validators.py
Last Updated: 2026-07-06
Overview
Validators enforce the shape of Sanity-authored payloads before they are persisted. Two families exist:
- Permit validators validate the
payloadattached to astorePermitentry onStore.regulatedCompliance.permits(mirrored toStoreComplianceProfile.permits). - Credential validators validate the
payloadattached to a credential entry inUser.credentials.
Each validator is a plain function that accepts a payload dict and either raises ValueError with a message naming the offending field, or returns the normalised payload dict. Services look up validators by type string in the relevant VALIDATORS registry; unknown types are treated as errors at the caller layer.
The generic and cannabis-specific permit validator modules both define a VALIDATORS registry. A dispatcher in the generic module composes the two so callers have a single entrypoint.
Permit Validators (Generic)
Shared _require(payload, field) helper raises ValueError('Missing required field: <field>') if the key is absent or its value is an empty string (after stripping).
| Type | Validator | Returned fields |
|---|---|---|
certificate_of_operation | validate_certificate_of_operation | jurisdiction |
local_permit | validate_local_permit | jurisdiction |
tax_vendor_license | validate_tax_vendor_license | jurisdiction, accountNumber |
excise_tax_registration | validate_excise_tax_registration | jurisdiction, accountNumber |
insurance | validate_insurance | carrier, policyNumber, optional coverageAmount (coerced via float(); raises if non-numeric) |
All five are bundled into the module-level VALIDATORS dict.
validate_permit_payload(permit_type, payload)
Single dispatcher. Looks up permit_type in the generic VALIDATORS registry, then falls back to the cannabis registry (imported lazily to avoid circular import). Raises ValueError('Unknown permit type: ...') when neither registry recognises the type.
Permit Validators (Cannabis)
State-agnostic: enforces structural presence of fields (e.g. licenseClass, jurisdiction must be non-empty strings) without encoding any single jurisdiction's class-code taxonomy. Operators enter their regulator's class code verbatim.
| Type | Validator | Returned fields |
|---|---|---|
cannabis_retail_license | validate_cannabis_retail_license | licenseClass, jurisdiction |
metrc_registration | validate_metrc_registration | facilityId |
When a future engagement needs per-jurisdiction validation: add a sibling module under compliance/cannabis/states/<state>/permit_validators.py and compose it into the dispatcher. Do not overload the shared registry with a single jurisdiction's enum.
Credential Validators (Cannabis)
Called by RegulatedCustomerOnboardingService.create_with_credentials() through the VALIDATORS dict in compliance/cannabis/services/credential_validators.py. Unknown credential types are rejected by the service before reaching this module.
validate_government_id_verification(payload)
payloadmust be a dict.- Required keys:
documentType,dateOfBirth(both truthy). - Returned payload keeps
documentType,dateOfBirth, and passes throughverifiedAtandverifiedByUserIdif present.
validate_medical_marijuana_patient_card(payload)
payloadmust be a dict.- Required key:
patientId(truthy). - Returned payload:
patientId,programName(defaults to''),caregiverAllowed(coerced viabool()).
Registry: VALIDATORS = {'government_id_verification': ..., 'medical_marijuana_patient_card': ...}.