TotalApp Docs

Customs Engine

The server-side engine behind the Customs & Global Trade Operations add-on — a jurisdiction Strategy Pattern for tariff-classification forms, plus eleven AI reasoning endpoints that power the "Stage 2" half of every hybrid Embedding+AI screen in the add-on.

What Is the Customs Engine?

server/engines/CustomsEngine.ts is the single server-side file that backs every AI-calling and jurisdiction-aware screen in the Customs & Global Trade Operations add-on. It has two distinct jobs living in one file:

  • A jurisdiction Strategy Pattern (its oldest part, mirroring Patent Agent's and Compliance Engine's shape) — given a country code and a schema category, it picks the matching CustomsSchemaStrategy (Turkey/GTİP, United States/HTS, or a generic default) and returns a ready-to-render broker-authorization, Incoterms-rule, or tariff-classification form.
  • Eleven independent AI reasoning endpoints, one per screen in the add-on, each following the exact same shape: build a prompt from the request, route it to Cohere / Anthropic API / Claude CLI depending on apiMode, parse the model's JSON response, and log the result via the Event & Analytics Engine.

In one sentence

Customs Engine is where every "Stage 2" AI call in Customs & Global Trade Operations actually runs — the frontend's job is only to find the right context via local embeddings first (Stage 1) and hand it to one of this engine's eleven endpoints.

The Two-Stage Hybrid Pattern This Engine Completes

Every AI-calling screen in Customs & Global Trade Operations runs the same two-stage flow, and Customs Engine is always Stage 2:

Stage 1 — Embedding Engine (client, local Ollama only) Stage 2 — Customs Engine (this file, writerEngine-routed)
StageWhere it runsWhat it does
Stage 1Entirely client-side, in src/services/customsEmbeddingService.ts, which wraps the shared Knowledge EngineVectorizes the screen's query text against the tenant's own live ledger (sanction rules, declarations, clearance records, freight benchmarks, and so on) via the user's own local Ollama instance (ollamaEngine.embed + cosine similarity), never touching the server.
Stage 2Client-side (callClaude()) or server-side (this engine), depending on the user's Settings → Agentic choiceTakes the top Stage-1 matches and reasons over them — risk scoring, overcharge/overpayment detection, discrepancy diagnosis, etc. Only reached on the server when writerEngine is 'api' or 'local-cli'.

Why does Stage 1 never reach the server?

ollama/local-llm/web-llm all run in the user's own browser or on their own machine — the Render server has no network path to a user's 127.0.0.1:11434 Ollama instance. If a client function skipped this check and always called the server, selecting Ollama in Settings would silently produce confusing 500s instead of routing locally. This is documented project-wide as the "Full Writer Engine Support Rule," and every one of Customs Engine's eleven client-side counterparts implements it.

The candidate pool is always the tenant's own ledger

Unlike a few other engines in TotalApp that rank against a small static example corpus, every Customs screen's Stage-1 search ranks the tenant's own live records — there is no separate external regulation corpus (a real OFAC/EU/UN sanctions archive, a real government tariff schedule, a real carrier SLA database) wired in yet. "Search Similar Rules" on the Trade Compliance screen, for instance, semantically ranks the sanction rules already on file rather than a real government list.

Part 1 — Jurisdiction Schema Strategy

resolveCustomsStrategy(countryCode) upper-cases the incoming code and switches between three concrete CustomsSchemaStrategy classes, each implementing one method: getSchema(category).

StrategyJurisdictionStatute referenced
TRCustomsStrategyTurkey (GTİP — 12-digit tariff code)6769 Sınai Mülkiyet Kanunu, TSE/CE/Tareks permit fields
USCustomsStrategyUnited States (HTS — 10-digit tariff code)FDA/EPA/FCC permit fields
DefaultCustomsStrategyAny unmapped countryGeneric fallback fields, never throws

Each strategy branches again on category, returning one of three different field sets:

broker_agency

Customs registration number, Power of Attorney reference and expiry date, digital signature certificate number, and assigned customs offices — powers the Customs Brokers & Agencies screen's authorization modal.

incoterms_rules

Incoterm code, risk transfer point, buyer freight-cost share (%), buyer risk share (%), and (TR only) a VAT exemption code — powers the Incoterms & Delivery Rules screen.

Default (tariff classification)

HS/GTİP or HTS code, product description, duty rate (%), excise rate (%, TR only) and required permits/certifications — powers the HS Code Catalog screen.

The resulting UiComponent Form tree is rendered by the same <DynamicScreenRenderer> component every other server-driven-UI schema in TotalApp uses — served by handleCustomsEngineGenerateSchema at POST /api/customs-engine/generate-schema.

Part 2 — The Eleven AI Reasoning Endpoints

Every AI-calling screen in Customs & Global Trade Operations has its own dedicated handler function in this file. All eleven share one identical shape — build prompt, route by apiMode (Cohere command-r7b-12-2024 / Anthropic claude-sonnet-4-6 / Claude CLI), parse the model's JSON, log via Event & Analytics Engine — and differ only in their prompt content and output schema:

HandlerEndpointScreen it powersWhat it computes
handleCustomsEngineTradeComplianceAudit/customs-engine/trade-compliance-auditTrade Compliance & SanctionsDenied-party match, dual-use license requirement, military-use risk vector, and required documentation for a proposed export/shipment.
handleCustomsEngineExportDeclarationAudit/customs-engine/export-declaration-auditExport DeclarationsMissing invoice details, dual-use export flag, and currency valuation variance note for an outbound (ETGB) filing.
handleCustomsEngineImportDeclarationAudit/customs-engine/import-declaration-auditImport DeclarationsUndervaluation risk warning, reference price gap (%), and a TAREKS safety-inspection hold recommendation.
handleCustomsEngineClearanceRiskAudit/customs-engine/clearance-risk-auditClearance StatusRouting motive, predicted additional delay in hours, and whether a physical (Red Line) inspection is likely.
handleCustomsEngineOriginDocValidationAudit/customs-engine/origin-doc-validation-auditCertificates & Origin DocsRegional Value Content threshold check, non-originating material risk, and chamber-endorsement verification for a preferential origin document.
handleCustomsEngineCarrierRateAudit/customs-engine/carrier-rate-auditFreight Rate BenchmarkCarrier overcharge detection, spot-rate arbitrage opportunity, and an estimated price leakage amount for a shipping lane.
handleCustomsEngineDemurrageRiskAudit/customs-engine/demurrage-risk-auditGlobal Shipment TrackingPredicted arrival variance, free-time expiration urgency, and a recommended container gate-out priority.
handleCustomsEngineFreightBookingAudit/customs-engine/freight-booking-auditCarrier & Forwarder HubSLA contract-breach detection, booking allocation drop detection, and a hazardous-cargo handling accuracy estimate for a logistics provider.
handleCustomsEngineLandedCostAudit/customs-engine/landed-cost-auditLanded Cost CalculatorDuty-spike detection, misallocated terminal-charge flag, and a sanity-check recalculated per-unit landed cost.
handleCustomsEngineDutyLedgerAudit/customs-engine/duty-ledger-auditCustoms Duty & Tariff LedgerOverpayment detection, drawback (refund) eligibility, miscalculated tariff-bracket flag, and an estimated refund amount.
handleCustomsEngineDocMatcherLinterAudit/customs-engine/doc-matcher-linter-auditDoc Matcher & LinterNet weight gap, HS code mismatch, and currency discrepancy flags — via a two-pass Linter (raise candidates) → Critic (confirm/discard) prompt structure, folded into one call.

Why eleven near-identical handlers instead of one generic one?

Each handler's prompt is domain-specific — a sanctions-screening prompt and a duty-drawback prompt ask the model to reason about completely different things and return a different JSON shape. Keeping them as separate, independently testable functions (rather than one parameterized mega-handler) means a change to one screen's reasoning logic can never silently affect another's, at the cost of some repeated routing boilerplate that is intentional, not accidental duplication.

How Server-Side Routing Works — apiMode

Every one of the eleven handlers applies the exact same routing logic once a request actually reaches the server (meaning writerEngine was 'api' or 'local-cli' on the client — Ollama/local-llm/web-llm never send a request here at all):

apiModeRouteModel
'cohere'Cohere Chat APIcommand-r7b-12-2024
anything else, with ANTHROPIC_API_KEY setAnthropic Messages APIclaude-sonnet-4-6
anything else, no API key configuredLocal Claude CLI (spawn('cmd', ['/c','claude','--print']) on Windows)Whatever the CLI session resolves to

Every completed or failed call — regardless of which of the three paths served it — is logged via logEvent() to the Event & Analytics Engine with engineName: 'CustomsEngine', a per-handler workflowId (e.g. 'customs-engine-duty-ledger-audit'), and a duration measurement, so every screen's AI usage shows up in the same tenant-wide analytics history as every other engine in TotalApp.

Where the Customs Engine Fits

This engine has no persistence layer of its own — every screen's ledger data (sanction rules, declarations, clearance records, freight benchmarks, duty entries, and so on) lives in its own tenant-scoped JSON store under customs-trade/, managed by src/services/customsTradeService.ts, not by this engine. Customs Engine's job is strictly the reasoning step: given the context a screen already assembled (via its own Stage-1 embedding search), produce a structured judgment and hand it back.

On the client, every one of the eleven corresponding functions in customsTradeService.ts (runTradeComplianceAudit, runExportDeclarationAudit, runImportDeclarationAudit, runClearanceRiskAudit, runOriginDocValidationAudit, runCarrierRateAudit, runDemurrageRiskAudit, runFreightBookingAudit, runLandedCostAudit, runDutyLedgerAudit, runDocMatcherLinterAudit) reads writerEngine from useUIStore first, calls callClaude() directly for the three local engines, and only calls this server engine's matching endpoint for 'api'/'local-cli'.

A reasoning layer, not a data layer

Customs Engine never reads or writes a tenant's stored records directly — it receives exactly the context the calling screen assembled in its request body, reasons over it, and returns a result. Persisting that result back into the screen's own ledger is the calling screen's own responsibility, via customsTradeService.ts. In practice, screens differ on whether their row actions (as opposed to the AI diagnostic itself) persist anything — see the FAQ below.

Frequently Asked Questions

Why does this one file contain both a jurisdiction strategy pattern and eleven unrelated AI endpoints?
The jurisdiction strategy pattern (broker/Incoterms/tariff-classification forms) was written first, mirroring Patent Agent's and Compliance Engine's shape. As each new screen in the Customs & Global Trade Operations add-on needed its own Stage-2 AI reasoning endpoint, its handler was added to this same file rather than creating a new one-off engine file per screen — keeping every server-side piece of the add-on in one place.
Is this engine specific to the Customs & Global Trade Operations add-on?
Today, yes — all eleven AI endpoints and all three schema categories exist to serve that add-on's screens specifically. Nothing about the Strategy Pattern or the apiMode routing shape is add-on-specific, though; both patterns are the same ones Patent Agent, Compliance Engine and Matrix Agent already use elsewhere in TotalApp. A handler cannot simply be imported by another add-on, since each one's request/response shape and prompt are hand-written for its own screen's data model.
Do row actions like "Submit Drawback Refund Claim" or "Lock Landed Cost Allocation" always update the underlying record?
No — and this is not consistent across the add-on's screens. Some helpers (e.g. submitDutyRefundClaim, transmitExportDeclaration, updateProviderSLATier) generate a receipt reference and persist a real status change on the record. Others (e.g. lockLandedCostAllocation, applyDocLinterFix, enforceTradeSanctionHold) only produce a toast notification with a receipt reference — the underlying record is untouched, and a page refresh reverts any visual "locked/fixed" state. Neither class is wired to a real external system (a government filing gateway, a bank, a chamber of commerce) yet; both are placeholders pending a future integration.
What is the "Linter/Critic" two-pass structure on the Doc Matcher & Linter endpoint?
It is a single prompt that asks the model to work in two internal passes before answering: first raise every candidate cross-document discrepancy it can find (the "Linter" pass), then review its own candidates and discard anything that isn't a genuine mismatch — rounding noise, unit-conversion artifacts — before finalizing a verdict (the "Critic" pass). It borrows its terminology from Matrix Agent's own Gatekeeper/Scoring pipeline, but shares no code with it — the two engines' data shapes (a weighted numeric score vs. a set of boolean discrepancy flags) are different enough that only the two-phase prompt structure, not any function, was reused.
How would a new jurisdiction, like a hypothetical EU/EORI strategy, get added to Part 1?
By adding a new class implementing CustomsSchemaStrategy (e.g. EUCustomsStrategy) and one new branch in resolveCustomsStrategy()'s conditional chain. No screen calling the schema endpoint needs to change — they already pass whatever countryCode the user's jurisdiction picker resolves to. Note this engine currently supports fewer jurisdictions (TR/US/Default) than Patent Agent's five (TR/US/WO/EP/Default) — EU and WIPO customs forms both fall back to the generic Default strategy today.