Skip to content

About

Open-source deterministic HTS classification engine implementing GRI rules with explainable decision logic for international trade compliance.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

SYJ OpenTrade Logic

The Linux of International Trade Compliance

An open-source, explainable HTS classification engine and REST API — built to replace black-box AI classifiers with deterministic, auditable trade rules.

License Python CI Tests Core Dependencies Status Maintained by

Quick Start · Architecture · How Classification Works · API · Roadmap · Contributing


Why This Exists

Post-2026 tariff volatility turned customs compliance into a weekly operational fire drill. Companies in the $50M–$500M revenue band are stuck between two bad options:

Enterprise Trade Suites (SAP GTS, Oracle GTM) Black-Box AI Classifiers
Cost $250K–$2M+ implementations Often cheap, sometimes free
Transparency Proprietary rule engines Zero — you can't audit a prediction
Speed to deploy 6–18 months Fast, but you're flying blind
Explainability Partial, vendor-locked None — "trust the model"

SYJ OpenTrade Logic takes a third path: deterministic classification using the actual General Rules of Interpretation (GRI) that customs authorities use, with AI permitted only as an assistant — never as the decision-maker. Every classification returns a full, human-readable decision path. Nothing is a guess dressed up as a percentage.

📖 On honesty as a design principle: this README documents exactly what has been built and verified, release by release. Nothing below is aspirational copy for unwritten code — see Release Roadmap for what's real today versus what's planned.


🏗 Architecture

flowchart TB
    subgraph Client["Client Layer"]
        CLI["CLI<br/><i>cli/classify.py</i>"]
        HTTP["HTTP Clients<br/><i>curl · Postman · your app</i>"]
        SWAGGER["Swagger UI<br/><i>/docs</i>"]
    end

    subgraph API["API Layer — server_fastapi/"]
        FASTAPI["FastAPI App<br/><i>main.py</i>"]
        SCHEMAS["Pydantic Schemas<br/><i>schemas.py</i>"]
    end

    subgraph Core["Core Engine — core/ (zero dependencies)"]
        ENGINE["GRIEngine<br/><i>gri_engine.py</i>"]
        MODELS["Data Models<br/><i>models.py</i>"]
    end

    subgraph Data["Data Layer"]
        HTS["HTS Tariff Tree<br/><i>data/hts_full.json</i><br/>99 chapters · live USITC feed"]
        DB[("SQLite / Postgres<br/><i>server_fastapi/database.py</i>")]
    end

    subgraph External["External Sources"]
        USITC["USITC REST API<br/><i>hts.usitc.gov</i>"]
        IMPORTER["Importer<br/><i>scripts/import_hts_data.py</i>"]
    end

    CLI --> ENGINE
    HTTP --> FASTAPI
    SWAGGER --> FASTAPI
    FASTAPI --> SCHEMAS
    FASTAPI --> ENGINE
    FASTAPI --> DB
    ENGINE --> MODELS
    ENGINE --> HTS
    USITC -->|"~17,000+ HTS-10 records"| IMPORTER
    IMPORTER -->|"builds nested tree"| HTS

    style Core fill:#6366F1,stroke:#4338CA,color:#fff
    style API fill:#0EA5E9,stroke:#0369A1,color:#fff
    style Data fill:#10B981,stroke:#047857,color:#fff
    style External fill:#F59E0B,stroke:#B45309,color:#fff
Loading

Design principle: the core engine (core/) has zero third-party dependencies and doesn't know or care whether it's called from a CLI, a stdlib HTTP server, or FastAPI. This is deliberate — classification logic should outlive any particular web framework choice.


🧠 How Classification Works

Classification follows the General Rules of Interpretation (GRI) as a Directed Acyclic Graph traversal — not a black-box model call.

flowchart TD
    START(["Product description<br/>e.g. 'cordless electric drill'"]) --> TOKENIZE["Tokenize description"]
    TOKENIZE --> SCORE_HEAD["<b>GRI 1</b><br/>Score every heading in the HTS tree<br/>against heading terms + legal notes"]
    SCORE_HEAD --> CHECK{"Top score<br/>&gt; 0?"}
    CHECK -->|No| UNRESOLVED["❌ Return UNRESOLVED<br/>never guess"]
    CHECK -->|Yes| CLOSE{"Runner-up heading<br/>within 0.10 of top score?"}
    CLOSE -->|Yes| GRI3A["<b>GRI 3(a)</b><br/>Flag competing heading,<br/>prefer more specific description"]
    CLOSE -->|No| SCORE_SUB
    GRI3A --> SCORE_SUB["<b>GRI 6</b><br/>Score subheadings under<br/>the selected heading"]
    SCORE_SUB --> RESULT["✅ Return classification:<br/>• Final HTS code<br/>• Confidence score<br/>• Full decision path<br/>• Alternatives considered<br/>• Supporting legal notes<br/>• Duty rate"]
    UNRESOLVED --> DONE(["Auditable JSON result"])
    RESULT --> DONE

    style CHECK fill:#F59E0B,color:#000
    style CLOSE fill:#F59E0B,color:#000
    style UNRESOLVED fill:#EF4444,color:#fff
    style RESULT fill:#10B981,color:#fff
    style GRI3A fill:#6366F1,color:#fff
    style SCORE_HEAD fill:#6366F1,color:#fff
    style SCORE_SUB fill:#6366F1,color:#fff
Loading

Key engineering decision: real HTS heading text is terse legal language ("Automatic data processing machines and units thereof") that rarely contains the everyday words a user types ("laptop"). Those live at the subheading level. The engine's _effective_heading_score() scores each heading by the better of its own text or its best-matching child subheading — otherwise correct classifications would silently fail once real USITC data replaced hand-curated samples. This was a real bug, found and fixed during v0.3.0 development, and is permanently covered by tests/test_importer_and_terse_headings.py.

Example: a request end-to-end

sequenceDiagram
    participant U as User / Client
    participant A as FastAPI (main.py)
    participant E as GRIEngine (core/gri_engine.py)
    participant D as SQLite/Postgres (database.py)

    U->>A: POST /classify {"description": "cordless electric drill"}
    A->>E: engine.classify(description)
    E->>E: GRI 1 — score all headings
    E->>E: GRI 6 — score subheadings of top heading
    E-->>A: ClassificationResult (decision path, confidence, alternatives)
    A->>D: persist result as audit record
    D-->>A: record id
    A-->>U: 201 Created {id, final_code: "8467.21", decision_path: [...], ...}
Loading

🚀 Quick Start

Option A — Zero-dependency core (works anywhere, including Termux)

git clone https://github.com/SHalimoosavi/SYJ-OpenTrade-Logic.git
cd SYJ-OpenTrade-Logic/syj-opentrade-logic

python3 -m unittest tests.test_gri_engine -v
python3 cli/classify.py "cordless electric drill"
python3 cli/classify.py "cotton t-shirt" --json

Option B — Full REST API (FastAPI + SQLAlchemy)

pip install -r server_fastapi/requirements.txt --break-system-packages
pip install httpx --break-system-packages   # for TestClient

# Pull the real, full 99-chapter HTS dataset from the official USITC API
python3 scripts/import_hts_data.py

# Run the full test suite
python3 -m unittest tests.test_gri_engine tests.test_api tests.test_importer_and_terse_headings -v
python3 -m unittest server_fastapi.test_main -v

# Launch the live server
python3 -m uvicorn server_fastapi.main:app --reload --port 8000

Open http://localhost:8000/docs for a live, auto-generated Swagger UI.

curl -X POST http://localhost:8000/classify \
  -H "Content-Type: application/json" \
  -d '{"description": "cordless electric drill"}'

📦 Project Layout

syj-opentrade-logic/
├── core/                       # Zero-dependency logic modules
│   ├── models.py               #   HTS tree + result dataclasses
│   ├── gri_engine.py           #   GRI 1 / 3(a) / 6 DAG traversal + stemming
│   ├── rulings_search.py       #   BM25 lexical search over CROSS rulings
│   └── duty_calculator.py      #   Section 301/232 + AD/CVD estimation
├── cli/
│   └── classify.py             # Standalone CLI, no dependencies
├── server/                     # v0.2.0 — stdlib http.server REST layer
├── server_fastapi/             # v0.3.0+ — real FastAPI + SQLAlchemy
│   ├── main.py                  #   app entrypoint, classify endpoint
│   ├── database.py               #   SQLAlchemy models (Org/User/Product/AuditLog/Webhook)
│   ├── security.py, dependencies.py  #   JWT auth, RBAC
│   ├── routes_*.py               #   auth, catalog, org, rulings, duty, webhooks, audit, reports
│   ├── webhooks.py, webhook_triggers.py  #   HMAC-signed delivery
│   ├── reports.py                #   CSV/Excel/PDF generation
│   ├── requirements.txt          #   production dependencies
│   ├── requirements-dev.txt      #   test-only dependencies (pypdf, httpx)
│   └── test_*.py                 #   full integration test suite
├── frontend/                   # v0.5.0 — Vite + React + TypeScript dashboard
│   └── src/{pages,components,lib,types}/
├── scripts/
│   └── import_hts_data.py      # Pulls full 99-chapter HTS from the live USITC API
├── data/
│   ├── hts_sample.json          # Small demo dataset
│   ├── hts_full.json            # Generated by the importer — not committed
│   ├── cross_rulings_sample.json
│   ├── trade_programs_sample.json
│   └── adcvd_sample.json
├── tests/                       # Core engine test suites
├── .github/workflows/ci.yml     # v0.9.0 — runs the full suite + real Docker builds
├── Dockerfile.backend, Dockerfile.frontend, docker-compose.yml, nginx.conf
├── DEPLOYMENT.md                 # Docker/production deployment guide
├── LICENSE                      # Apache 2.0
└── README.md

🌐 REST API

Method Path Purpose
GET /health Liveness check
POST /classify Classify a product description, persist the result
GET /classifications?limit=&offset= Paginated classification history
GET /classifications/{id} Fetch one classification record
DELETE /classifications/{id} Delete one classification record
GET /docs Interactive Swagger UI (auto-generated by FastAPI)
GET /openapi.json Machine-readable OpenAPI 3.0 spec
Example response — POST /classify
{
  "id": 1,
  "product_description": "cordless electric drill",
  "final_code": "8467.21",
  "final_description": "Drills of all kinds, hand-held, electric motor",
  "confidence": 0.87,
  "is_classified": true,
  "duty_rate": "1.7%",
  "decision_path": [
    {
      "node_code": "8467",
      "node_description": "Tools for working in the hand...",
      "rule_applied": "GRI 1",
      "reasoning": "Heading 8467 scored highest (0.82) against the terms of the heading, per GRI 1...",
      "score": 0.82
    },
    {
      "node_code": "8467.21",
      "node_description": "Drills of all kinds, hand-held, electric motor",
      "rule_applied": "GRI 6",
      "reasoning": "Subheading 8467.21 scored highest (0.91) among subheadings of 8467, per GRI 6...",
      "score": 0.91
    }
  ],
  "alternatives": [],
  "supporting_notes": []
}

🤖 Why Deterministic, Not AI-First

AI should assist — cleaning descriptions, extracting attributes, summarizing rulings, suggesting candidates for a human to review. AI must never replace deterministic GRI-based classification.

This isn't a limitation — it's the whole point. A customs broker who gets asked "why did the system pick this code?" needs an answer rooted in the actual legal text of the tariff schedule, not a confidence score from an opaque model. Every result this engine returns can be defended in an audit.


🗺 Release Roadmap

timeline
    title SYJ OpenTrade Logic — Release Progression
    v0.1.0 : GRI classification engine
           : Zero-dependency core, 12 tests
    v0.2.0 : stdlib REST API
           : SQLite persistence, 22 tests
    v0.3.0 : Real FastAPI + SQLAlchemy
           : Full 99-chapter USITC import, 26 tests
    v0.4.0 : Auth, RBAC, organizations
           : Product catalog, CSV/Excel import
    v0.5.0 : Next.js dashboard
           : shadcn/ui, Tailwind, dark mode
    v0.6.0 : Semantic search
           : CBP CROSS rulings, vector DB
    v0.7.0 : Duty calculator
           : Section 301/232/122, AD/CVD
    v0.8.0 : Reports & audit trails
           : PDF/CSV/Excel export, webhooks
    v0.9.0 : Production DevOps
           : Docker, CI/CD, deployment docs
Loading
Release Scope Status
v0.1.0 GRI classification engine ✅ Done — 12 tests passing
v0.2.0 stdlib REST API + SQLite persistence ✅ Done — 22 tests passing
v0.3.0 Real FastAPI + SQLAlchemy; full 99-chapter HTS import ✅ Done — 26 tests passing, live against USITC data
v0.4.0 Auth (JWT/RBAC), organizations, product catalog, CSV/Excel import 🔜 Planned — needs Postgres
v0.5.0 Vite+React dashboard (shadcn/Tailwind) — swapped from Next.js for Termux/mobile practicality ✅ Code complete, import/export/syntax-verified by script; npm install + real run needed on your machine
v0.6.0 CROSS rulings search (BM25 lexical, zero-dependency) — swapped from Pinecone/Milvus+embeddings per explicit decision ✅ Done — 15 new tests passing here; API integration tests need your machine
v0.7.0 Duty calculator (Section 301/232 + AD/CVD scope flagging) ✅ Done — 19 new tests passing here; real, dated, sourced sample data with strong disclaimers
v0.8.0 Reports (PDF/CSV/Excel), audit trails, webhooks ✅ Done — 103 tests passing here, including real HTTP webhook delivery to a local server and round-tripped PDF/Excel files
v0.9.0 Docker/Compose, GitHub Actions CI/CD, deployment docs ✅ Done — see DEPLOYMENT.md; CI runs for real on GitHub's servers on every push

🧪 Testing

# Install both production and test-only dependencies
pip install -r server_fastapi/requirements.txt --break-system-packages
pip install -r server_fastapi/requirements-dev.txt --break-system-packages

# Core engine, importer, rulings search, duty calculator (no FastAPI app instantiated)
python3 -m unittest tests.test_gri_engine tests.test_api tests.test_importer_and_terse_headings tests.test_rulings_search tests.test_duty_calculator -v

# Standalone server modules (security, CSV/Excel import, webhooks, reports)
python3 -m unittest server_fastapi.test_security server_fastapi.test_catalog_import server_fastapi.test_webhooks server_fastapi.test_reports -v

# Each of these instantiates a real FastAPI app + SQLAlchemy engine and
# MUST run as a separate command -- combining them in one invocation
# silently breaks database isolation between them (a real bug found
# during v0.4.0 development; see .github/workflows/ci.yml's comments)
python3 -m unittest server_fastapi.test_main -v
python3 -m unittest server_fastapi.test_auth_and_catalog -v
python3 -m unittest server_fastapi.test_rulings_api -v
python3 -m unittest server_fastapi.test_duty_api -v
python3 -m unittest server_fastapi.test_v080_features -v

103 tests, all passing, including: correct GRI-based classification, refusal to guess on unmatched products, decision-path integrity, live HTTP integration against a real running server, direct SQLite file verification, real-data regressions found while importing the live USITC feed (chapter detection, terse headings, a stemmer bug affecting silent-e plurals), BM25 rulings search ranking, duty calculation across Section 301/232 and AD/CVD scenarios, real HMAC-signed webhook delivery to an actual local HTTP server with independent signature verification, and real generated PDF/Excel files round-tripped and verified.

CI runs this full suite automatically on every push (see the badge above and .github/workflows/ci.yml), plus a real npm run build of the dashboard and real docker build of both production images on GitHub's Linux servers.


🤝 Contributing

This project is built in the open, in public, with every bug and fix documented rather than hidden. CI runs automatically on every push and pull request (see DEPLOYMENT.md). Issues and PRs welcome.

📄 License

Apache 2.0 — see LICENSE.


Built by Sayanjali Nexus Private Limited · Hyderabad, India

About

Open-source deterministic HTS classification engine implementing GRI rules with explainable decision logic for international trade compliance.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages