Rule of Origin Logic Engines

A rule of origin logic engine is the deterministic evaluation layer that decides whether a finished good qualifies for preferential treatment under a trade agreement, translating change-in-tariff-classification (CTC) rules, regional value content (RVC) thresholds, and de minimis tolerances into an auditable decision graph. Part of the Core Architecture & Tariff Mapping reference architecture, the engine sits between classification and duty calculation: it consumes a normalized bill of materials, resolves origin status against a specific agreement version, and emits a determination that either unlocks a preferential rate or falls back to Most-Favored-Nation (MFN) treatment. For trade compliance officers filing a claim under USMCA, CPTPP, or the EU–UK TCA, the answer must be reproducible and traceable to a named legal provision; for the Python ETL teams who build the pipeline, that same answer must be a pure function of its inputs — no hidden state, no heuristic guessing, no floating-point drift in the RVC arithmetic.

Problem Framing: Silent Origin Misqualification

The failure mode this engine exists to prevent is silent origin misqualification — a preferential claim that passes internal checks yet does not survive a CBP verification because the rule applied was not the rule in force, or was applied in the wrong precedence. Three specific defects recur in hand-built origin logic. First, precedence collapse: a product-specific rule (PSR) that requires both a tariff shift and an RVC minimum is coded as an either/or, so a good qualifies on RVC alone when the PSR demanded the shift too. Second, de minimis over-reach: the tolerance is applied to the wrong denominator (transaction value vs. adjusted value) or to a good whose chapter is explicitly excluded from de minimis, inflating the qualifying share. Third, version drift: the agreement annex used at evaluation time lags the annex that was legally effective on the date of production, so a shift rule that changed under a mid-term review is silently mis-scored.

Each defect produces a claim that looks correct in the broker’s system and is denied months later, triggering duty recovery, interest, and — under repeated findings — a loss of the importer’s known-shipper standing. The engine below closes those gaps by making precedence explicit, routing ambiguity to an exception state rather than a guess, and binding every determination to a versioned agreement identifier.

Correct precedence ladder versus the three silent-misqualification defects A two-part diagram. On the left, a vertical precedence ladder shows the correct evaluation order: product-specific rules and CTC shift first, then regional value content, then the bounded de minimis tolerance only as a last-resort fallback — each rung binding to a named, versioned agreement provision. On the right, three defect cards show how hand-built logic silently misqualifies a claim. Precedence collapse codes a rule requiring both a tariff shift and an RVC minimum as an either-or, so a good qualifies on RVC alone. De minimis over-reach applies the tolerance to the wrong denominator or to an excluded chapter, inflating the qualifying share. Version drift evaluates against a lagging annex, so a shift rule that changed under a mid-term review is mis-scored. Each defect produces a claim that passes internal checks but is denied at CBP verification. The correct path is shaded teal, the defect cards are shaded red. Correct precedence · bound to a versioned provision 1 · PSR / CTC shift product-specific rule — highest priority 2 · RVC threshold only when the shift is not required alone 3 · De minimis bounded fallback · not for excluded chapters Determination names the rule · binds the agreement version Silent misqualification · passes internally, denied at CBP Precedence collapse rule needs shift AND RVC, coded as OR → qualifies on RVC alone, shift skipped fix: require both, route ambiguity to exception De minimis over-reach wrong denominator or excluded chapter → inflates the qualifying share fix: bound the tolerance, exclude by chapter Version drift annex lags the date-of-production rule → mid-term-review shift mis-scored fix: bind determination to the ingested version Each defect yields duty recovery, interest, and lost known-shipper status.

Schema / Data Contract

Origin evaluation is only as trustworthy as the contract that guards its inputs. Before any rule fires, each material input, the finished good’s output HS code, and the transaction value must pass structural validation: HS codes constrained to the 6–10 digit range the WCO nomenclature permits, monetary values held as positive numbers, and every material tagged with an explicit originating flag rather than an inferred one. The Pydantic contract below formalizes that input surface and the provenanced result the engine returns, so a malformed BOM is rejected at the boundary instead of corrupting the RVC denominator downstream.

from __future__ import annotations
import logging
from dataclasses import dataclass, field
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field, ValidationError, field_validator

logger = logging.getLogger(__name__)

class OriginStatus(Enum):
    ORIGINATING = "ORIGINATING"
    NON_ORIGINATING = "NON_ORIGINATING"
    INDETERMINATE = "INDETERMINATE"

class EvaluationError(Exception):
    """Base exception for deterministic RoO evaluation failures."""
    def __init__(self, code: str, message: str, hs_code: Optional[str] = None):
        self.code = code
        self.message = message
        self.hs_code = hs_code
        super().__init__(f"[{code}] {message} (HS: {hs_code or 'N/A'})")

class MaterialInput(BaseModel):
    hs_code: str = Field(..., pattern=r"^\d{6,10}$")
    value_usd: float = Field(..., gt=0)
    is_originating: bool
    processing_country: str

class RuleOfOriginResult(BaseModel):
    status: OriginStatus
    applied_rule: str
    rvc_percentage: Optional[float] = None
    audit_trail: list[str] = Field(default_factory=list)

The MaterialInput model is the input contract for a single BOM tier; the RuleOfOriginResult model is the output contract every downstream consumer reads. Note that applied_rule is a required string, not an optional annotation — a determination that cannot name the rule it applied is treated as a defect, because an unnamed rule cannot be defended in a verification. The INDETERMINATE status exists precisely so that ambiguous inputs surface as an explicit terminal state rather than being coerced into a false ORIGINATING.

Step-by-Step Implementation

The engine evaluates in fixed passes and applies a strict precedence order — product-specific and CTC rules first, RVC second, de minimis only as a bounded fallback. Each pass records a line in the audit trail so a compliance officer can read the determination top to bottom. The evaluate method never returns without appending the reasoning that produced its verdict.

class RoOEngine:
    def __init__(self, agreement_id: str, rvc_threshold: float, de_minimis_pct: float = 10.0):
        self.agreement_id = agreement_id
        self.rvc_threshold = rvc_threshold
        self.de_minimis_pct = de_minimis_pct

    def evaluate_ctc(self, input_hs: str, output_hs: str) -> bool:
        """Validates Change in Tariff Classification against chapter/heading rules."""
        if len(input_hs) < 4 or len(output_hs) < 4:
            raise EvaluationError("INVALID_HS_LENGTH", "HS codes must be at least 4 digits", output_hs)
        
        input_chapter = input_hs[:2]
        output_chapter = output_hs[:2]
        return input_chapter != output_chapter

    def calculate_rvc(self, materials: list[MaterialInput], fob_value: float) -> float:
        """Computes Regional Value Content percentage."""
        if fob_value <= 0:
            raise EvaluationError("INVALID_FOB", "FOB value must be positive")
        
        non_originating_value = sum(m.value_usd for m in materials if not m.is_originating)
        rvc = ((fob_value - non_originating_value) / fob_value) * 100
        return round(rvc, 2)

    def evaluate(self, materials: list[MaterialInput], output_hs: str, fob_value: float) -> RuleOfOriginResult:
        """Deterministic multi-pass evaluation with explicit error routing."""
        audit: list[str] = []
        
        try:
            # Pass 1: CTC Evaluation — every non-originating input must satisfy the shift.
            non_originating = [m for m in materials if not m.is_originating]
            if not non_originating:
                ctc_met = True
                audit.append("CTC check: PASS (all inputs originating)")
            else:
                ctc_met = all(self.evaluate_ctc(m.hs_code, output_hs) for m in non_originating)
                audit.append(f"CTC check: {'PASS' if ctc_met else 'FAIL'} (evaluated {len(non_originating)} non-originating inputs)")
            
            # Pass 2: RVC Evaluation
            rvc = self.calculate_rvc(materials, fob_value)
            rvc_met = rvc >= self.rvc_threshold
            audit.append(f"RVC: {rvc}% (Threshold: {self.rvc_threshold}%) -> {'PASS' if rvc_met else 'FAIL'}")
            
            # Precedence: PSR > RVC > De Minimis
            if ctc_met:
                return RuleOfOriginResult(status=OriginStatus.ORIGINATING, applied_rule="CTC_SHIFT", audit_trail=audit)
            elif rvc_met:
                return RuleOfOriginResult(status=OriginStatus.ORIGINATING, applied_rule="RVC_THRESHOLD", rvc_percentage=rvc, audit_trail=audit)
            else:
                # De minimis fallback
                non_orig_pct = sum(m.value_usd for m in materials if not m.is_originating) / fob_value * 100
                if non_orig_pct <= self.de_minimis_pct:
                    audit.append(f"De minimis applied: {non_orig_pct:.2f}% <= {self.de_minimis_pct}%")
                    return RuleOfOriginResult(status=OriginStatus.ORIGINATING, applied_rule="DE_MINIMIS", audit_trail=audit)
                
                return RuleOfOriginResult(status=OriginStatus.NON_ORIGINATING, applied_rule="NO_RULE_MET", rvc_percentage=rvc, audit_trail=audit)
                
        except ValidationError as ve:
            logger.error("Schema validation failed during RoO evaluation", extra={"error": ve.errors()})
            raise EvaluationError("SCHEMA_VIOLATION", "Invalid material structure", output_hs) from ve
        except Exception as e:
            logger.critical("Unhandled evaluation exception", exc_info=True)
            raise EvaluationError("EVALUATION_FAILURE", "Deterministic evaluation interrupted", output_hs) from e

The stages map directly onto the agreement text. Stage 1 (CTC) iterates every non-originating input and requires each one to satisfy the tariff shift declared for the output heading; a single input that fails to shift fails the whole rule, which is why the check uses all(...) rather than any(...). Stage 2 (RVC) computes the regional value content on the transaction-value method and compares it to the agreement threshold. Stage 3 (precedence and de minimis) applies the fixed order and only reaches for the tolerance when both primary rules fail and the non-originating share sits within the statutory limit. Any schema violation or unexpected exception is caught and re-raised as a coded EvaluationError, so callers see a structured failure rather than a bare stack trace.

The precedence itself is worth stating explicitly, because getting the order wrong is the most common source of denied claims:

  1. Product-Specific Rules (PSRs) override general chapter rules.
  2. Chapter/Section Notes override regional agreement defaults.
  3. De Minimis Tolerances apply only when primary rules fail and explicit thresholds are met.

The evaluation state machine tracks material transformation across each BOM tier, maintaining a deterministic ledger of non-originating inputs, regional value percentages, and applicable tariff shifts. Ambiguous inputs trigger explicit exception states rather than heuristic guesses, ensuring compliance officers can trace every decision to a specific legal provision. For the full state-machine orchestration, worked USMCA examples, and multi-tier BOM handling, see Implementing rule of origin checks in Python.

Origin evaluation cascade with strict rule precedence A top-down decision flowchart tracing one bill of materials through the engine. It enters with the BOM, output HS code, and FOB value. First test: are there any non-originating materials? If none, the good is ORIGINATING under WHOLLY_OBTAINED. If some, the cascade runs in fixed precedence order. Test one, the change-in-tariff-classification shift: if it is satisfied for every non-originating input, the good is ORIGINATING under CTC_SHIFT. If not, test two, regional value content: if RVC is at or above the agreement threshold, the good is ORIGINATING under RVC_THRESHOLD. If not, test three, the bounded de minimis tolerance: if the non-originating share is under the limit, the good is ORIGINATING under DE_MINIMIS. If every test fails, the good is NON_ORIGINATING under NO_RULE_MET and falls back to the MFN rate column. Originating terminals are shaded teal, the non-originating terminal is shaded red, and the ordered decision diamonds are shaded gold to mark the strict precedence path. none some non-orig. shift met shift fails RVC ≥ threshold RVC below within limit over limit BOM · output HS · FOB value Any non-originating materials? 1 · CTC shift met for each? 2 · RVC ≥ threshold? 3 · Non-orig. share under de minimis? ORIGINATING WHOLLY_OBTAINED ORIGINATING CTC_SHIFT ORIGINATING RVC_THRESHOLD ORIGINATING DE_MINIMIS NON_ORIGINATING NO_RULE_MET · falls back to MFN column ordered precedence test originating terminal non-originating terminal

Validation & Determinism

Determinism is the property auditors actually test: the same BOM, agreement version, and rate tables must return the same determination on every replay, byte for byte in the audit trail. Several cross-checks enforce that guarantee. HS codes are constrained to the 6–10 digit range so a truncated 4-digit heading cannot masquerade as a full statistical code; the evaluate_ctc guard rejects anything shorter than the 4 digits a chapter-level shift requires. RVC is computed once, rounded to a fixed two-decimal precision that matches CBP ACE decimal handling, and reused — never recomputed with a different denominator between the primary pass and the de minimis check. The de minimis fallback compares against a bounded percentage rather than an open threshold, so an out-of-range tolerance cannot silently qualify a good.

Inputs that cannot be resolved deterministically must not be guessed. A material whose originating status is genuinely unknown, or an output HS code with no mapped rule under the active agreement, resolves to INDETERMINATE and is quarantined for review rather than defaulted to ORIGINATING. That quarantine routing is the same mechanism the HTS Schedule Database Design uses to hold records whose tariff lineage cannot be reconstructed, and it feeds the broker-review queue described under Fallback Routing for Unmapped Codes. Binding each determination to a hash of its inputs, the agreement version, and the evaluation parameters lets a verification reconstruct the exact decision that was made on the entry date, even after the agreement annex has since changed.

Downstream Integration

Once origin status is resolved, the result payload becomes an input to the rest of the tariff-mapping architecture. An ORIGINATING determination unlocks the preferential rate column and routes directly into the Duty Formula Calculation Frameworks, where the preferential ad valorem rate, any anti-dumping or countervailing margin, and excise adjustments are applied to produce the landed-cost figure. A NON_ORIGINATING determination falls back to the general (MFN) column in that same duty stage. When an output HS code has no mapped PSR or sits outside the agreement’s coverage entirely, the engine hands the record to Fallback Routing for Unmapped Codes, which defaults to MFN treatment while flagging the record for manual compliance review rather than fabricating a rule.

Upstream, the engine is only as current as its rule tables. The preferential agreement annexes, de minimis thresholds, and RVC baselines it evaluates against are synchronized by the Tariff Update Ingestion Pipelines from official gazettes and customs authority feeds; any latency, schema drift, or partial load in those feeds propagates directly into origin errors, which is why the engine binds every determination to the ingested agreement version it actually used.

Scaling & Resilience

Production RoO evaluations frequently process BOMs with thousands of line items across multi-tier supply chains, and a naive implementation that materializes the full tariff graph per evaluation exhausts memory during peak ingestion windows. Four controls keep the engine inside its footprint budget:

  • Streaming DAG traversal of the HS hierarchy instead of full-graph materialization, so only the ancestor path a given code needs is resident.
  • Lazy RVC evaluation — the value-content calculation runs only after the CTC pass fails, avoiding arithmetic on goods that already qualify on the tariff shift.
  • Connection pooling for tariff-schedule lookups with an LRU cache keyed on (agreement_id, hs_code), since the same headings recur heavily within a batch.
  • Batched state serialization using memory-mapped files for audit persistence, so the trail is durable without holding every determination in the heap.

For resilience under high-throughput clearance loads, evaluations are dispatched through a bounded async queue with a semaphore cap so a burst of filings cannot overwhelm the pooled schedule lookups, and upstream feed failures trip a circuit breaker that pauses evaluation rather than scoring against a stale annex. Transient ingestion errors are retried with backoff; a determination is never emitted against partially loaded rule tables. These patterns hold sub-100ms evaluation latency even when a batch window pushes tens of thousands of line items through the engine.

Compliance Obligations

Every RoO determination is evidence in a potential verification, so the audit trail is designed as an immutable, append-only record. Each stored determination carries the agreement identifier and version, the timestamp, cryptographic hashes of the input payload and evaluation parameters, the applied_rule, the computed RVC where relevant, and the full ordered audit_trail list the engine produced. Retention follows the CBP recordkeeping horizon — at least five years past the entry date, held on an immutable tier so a Focused Assessment can be answered from primary records rather than reconstructed after the fact.

Regulatory change is handled as first-class input, not an afterthought. When a Federal Register notice, a tariff bulletin, or an agreement’s mid-term review alters a shift rule, RVC threshold, or de minimis exclusion, the affected agreement version is superseded rather than overwritten, and in-flight claims scored against the prior version are flagged for re-evaluation. Any determination the engine cannot resolve deterministically — an INDETERMINATE status, a good in a de minimis-excluded chapter, an unmapped output code — is escalated to a broker through a human-in-the-loop gate rather than auto-qualified. This combination of immutable provenance, versioned rule binding, explicit precedence, and mandatory escalation is what turns dense preferential-agreement text into origin infrastructure that survives audit instead of merely passing internal checks.

Up: Core Architecture & Tariff Mapping