Compliance - Domain Overview

Location: api/nextango/apps/compliance/ Last Updated: 2026-07-06

Overview

The compliance app is the platform's industry-agnostic compliance scaffold. It defines abstract base models for purchase limit records, identity verification events, batch test results, state-track sync records, batch and store profiles, and sale/return snapshots. Concrete implementations live in a vertical sub-app (compliance.cannabis). The agnostic app also hosts the pieces that are genuinely vertical-neutral: the RegulatedCustomerOnboardingService orchestrator, the TaxClassificationService rate-filter engine, the KYCVendorAdapter seam, credential and permit read helpers, and the vertical-provider seam that resolves the active vertical at runtime.

Cannabis is the vertical shipping today. It is documented here as one vertical plugged into agnostic slots. The same slots hold a food-safety or pharmaceutical vertical without rewriting the agnostic core.

Architecture

The agnostic app names no industry vocabulary. Everything cannabis-specific (concrete models, tax tiers, purchase-limit ceilings, age-gate rules, recall handling, report generators) lives inside compliance/cannabis/. The two layers connect through three seams.

Abstract base models. compliance/models.py defines the field contract for each compliance concept. The cannabis container subclasses each base and adds vertical-specific fields and a db_table.

The vertical-provider seam. compliance/services/vertical_provider.py exposes get_active_vertical_provider(). Agnostic callers resolve the active vertical's provider through it and never import a cannabis module directly. The cannabis provider (CannabisComplianceProvider) supplies store credential policy, credential-payload validators, employee authorization, the pre-completion checkout gates, purchase-limit recording, the vertical's additional tax excise, and the store-profile write.

Vertical registration. The cannabis app registers itself from ComplianceCannabisConfig.ready() (compliance/cannabis/apps.py): it connects its signals, registers its report generators in the agnostic report registry, and registers its batch profile as a hard-delete blocker on products.InventoryLevel. An install without the cannabis app contributes none of these, and the agnostic models carry no cannabis import.

The compliance domain uses two patterns for storing vertical-specific data:

JSONField compliance slots live on core platform models (ProductVariant.cannabis_compliance, User.credentials, Store.regulatedCompliance.permits) for Sanity-authored content. The Sanity schema holds the editable data; the JSONField is the write path; denormalized flat columns on profile models are populated from it.

One-to-one profile models live in compliance/cannabis/models.py for indexed, queryable compliance state not held in Sanity. Each profile attaches to its core model through a stable reverse accessor.

Profile modelCore modelAccessor
StoreComplianceProfilestores.Storestore.compliance_profile
ProductVariantCannabisProfileproducts.ProductVariantvariant.cannabis_profile
InventoryLevelCannabisProfileproducts.InventoryLevelinventory_level.cannabis_profile
SaleTransactionCannabisProfiletransactions.SaleTransactiontransaction.compliance_profile

Sub-apps

PathPurpose
compliance/Abstract bases, agnostic services, the vertical-provider seam, KYC adapters, shared endpoints
compliance/cannabis/Cannabis vertical: concrete models, cannabis services, the compliance provider, Metrc integration, report generators

Endpoints

Customer-facing gate endpoints (purchase limits, age verification) keep their routes on the agnostic compliance/urls.py; their view classes live in the cannabis container. The public paths are unchanged.

MethodPathPurpose
GET/compliance/purchase-limits/status/Current purchase window status for a customer
POST/compliance/age-verification/Record an age verification attempt
GET/compliance/age-verification/status/Check session verification status
POST/compliance/onboarding/regulated-customer/POS employee-assisted onboarding
POST/compliance/onboarding/storefront-customer/Storefront self-registration (AllowAny)
POST/compliance/onboarding/credentials/<key>/approve/Approve a pending credential
POST/compliance/onboarding/credentials/<key>/reject/Reject a pending credential
GET/compliance/onboarding/pending-credentials/Store-scoped pending credentials listing
POST/compliance/kyc/persona/webhook/Persona KYC vendor webhook (HMAC-signed)
GET/compliance/cannabis/reports/List compliance reports
POST/compliance/cannabis/reports/Request a compliance report
GET/compliance/cannabis/reports/<id>/Poll report status
GET/compliance/cannabis/reports/<id>/download/Download a ready report file
POST/compliance/cannabis/purchase-orders/<pk>/metrc-verify/Transition a PO to RECEIVED after a compliance check

Key Services

Location tells you which layer owns the logic. Where an agnostic path still exists for a cannabis-canonical service, that path is a legacy-import compatibility shim holding no logic; the architectural route from agnostic code to vertical behavior is the get_active_vertical_provider() seam, not the shims.

ServiceLocationPurpose
RegulatedCustomerOnboardingServicecompliance/services/regulated_customer_onboarding_service.pyAtomic user + credential creation, channel-aware state, approve/reject workflow; delegates vertical rules through the provider seam
TaxClassificationServicecompliance/services/tax_classification.pyAgnostic rate-filter engine that filters a jurisdiction's rate components by compliance tier
get_active_vertical_providercompliance/services/vertical_provider.pyResolves the active vertical's compliance provider for agnostic callers
AgeVerificationServicecompliance/cannabis/services/age_verification.pyAge gate enforcement, session verification, medical-card validation (agnostic path is a compatibility shim)
CannabisTaxServicecompliance/cannabis/services/tax_classification.pyCannabis transaction-type classification and the Ohio vape excise
EmployeeComplianceServicecompliance/cannabis/services/employee_compliance_service.pyGate 0 check: DCC agent permit + cleared background check (agnostic path is a compatibility shim)
CannabisComplianceProvidercompliance/cannabis/services/compliance_provider.pyThe cannabis vertical's provider: store policy, validators, checkout gates, excise, profile write
ComplianceReportServicecompliance/services/report_service.pyReport request authorization and Celery task dispatch
PurchaseLimitEnginecompliance/cannabis/services/purchase_limit_engine.pyCannabis purchase limit gate and window tracking
CannabisRecallServicecompliance/cannabis/services/recall_service.pyRecall activation, destruction recording, resolution

Was this page helpful?