ChemTrace Pro

A Multi-Stakeholder Chemical Traceability Platform
From Import Declaration to Public and Environmental Safety: An Integrated Approach to Chemical Management Compliance in Suriname
Authors: Gabor Tjong A Hung, Donovan Bogor  |  June 2026  |  Version 1.0

ChemTrace Pro: A Multi-Stakeholder Chemical Traceability Platform

Subtitle: From Import Declaration to Public and Environmental Safety: An Integrated Approach to Chemical Management Compliance in Suriname

Authors: Gabor Tjong A Hung, Donovan Bogor

Date: June 2026

Version: 1.0


Executive Summary

ChemTrace Pro is an integrated chemical tracking and compliance platform built for Suriname's National Environment Authority (NMA) for the execution and control of Article 35 from the Environmental Framework Act. It replaces disconnected paper-based workflows with a unified system that traces every chemical container from the moment it is declared for import to the moment a first responder scans its QR code at an emergency scene.

The platform serves four distinct groups — NMA oversight staff, importing companies, warehouse inspectors, and the general public — through five purpose-built applications connected to a single source of truth. Rather than treating regulatory compliance as a back-office paperwork exercise, ChemTrace Pro embeds compliance into the physical supply chain: every can, cylinder, and drum will receive a unique digital identity encoded on both an RFID tag and a printed QR code — a capability the NMA has never had before. That identity binds the container to its import approval, its Safety Data Sheet (SDS), and its GHS hazard classification for its entire lifecycle.

This document presents the architecture, design rationale, and operational flows of ChemTrace Pro as a reference model for national-scale chemical traceability.


1. Problem Statement

Suriname's chemical import regulatory workflow has historically operated across disconnected systems: paper SDS binders, spreadsheet inventories, manual warehouse ledgers, and ad-hoc communication between consignees and regulators. For the NMA, this fragmentation creates critical operational gaps.

The Traceability Gap

A chemical container sitting on a warehouse floor had no reliable digital tether to its import declaration, its approval status, or its safety documentation. A regulator conducting an inspection could see a can of pesticide but could not confirm, in real time, whether that can was legally imported, whether its SDS had been reviewed, or whether it had been approved for release. The only way to answer those questions was to cross-reference multiple paper documents, often stored in different locations.

Slow and Error-Prone SDS Management

Safety Data Sheets arrived as PDFs, were filed manually, and were difficult to retrieve during inspections or emergencies. Extracting structured hazard data — GHS pictograms, H-codes, P-codes, UN transport numbers — was done by hand, introducing transcription errors and delays. In many cases, the extracted data never made it into a searchable digital format at all.

No Instant Hazard Awareness

Firefighters, medical personnel, or concerned citizens at an incident scene had no direct way to look up the contents of a chemical container. The information existed, but it was locked in PDFs or internal databases that required logins, phone calls, or physical visits to access. In an emergency, minutes matter.

Offline Inspections Were Impractical

Field inspectors conducted warehouse audits with clipboards and printed lists. Remote warehouses often lacked reliable mobile connectivity, so inspectors could not access live records during the audit. Discrepancies between declared and physical inventory were noted manually and transcribed later, creating a second opportunity for error.

Undeclared Imports Went Undetected

A warehouse might contain more physical inventory than was declared on import paperwork. Without scanning individual containers and comparing them against a digital manifest, there was no systematic way to detect undeclared or excess stock.

Absence of System-Generated Labels

The NMA does not currently produce RFID labels from approved batch data. Chemical containers arrive without scannable digital identities that bind them to their import approval and Safety Data Sheet. Introducing integrated label printing is a planned enhancement: labels will be generated only after batch approval and will reflect the exact approved data, preventing any mismatch between the physical label and the system record.

Who Is Affected

StakeholderPain Point
NMA oversight staffCannot verify compliance in real time
Consignees and importersMust navigate opaque, manual submission processes
Warehouse inspectorsWork offline with no digital manifest
First respondersNo instant access to hazard data at incident scenes
General publicCannot verify safety information independently

Existing generic ERP or inventory systems do not solve these problems because they treat chemicals as ordinary stock-keeping units. They lack native understanding of GHS classifications, UN numbers, regulatory approval workflows, or RFID label generation. ChemTrace Pro was therefore designed as a domain-specific platform rather than a configuration of generic software.


2. Background and Related Work

Regulatory Context

Chemical compliance sits at the intersection of environmental law, occupational safety, and supply-chain logistics. Two international frameworks are directly relevant to ChemTrace Pro's domain:

In practice, regulators must reconcile three data sources for every imported chemical: the import declaration, the manufacturer's SDS, and the physical container label. When these sources disagree — or when one is missing — enforcement becomes difficult and public safety is compromised.

Existing Approaches and Their Limitations

Several categories of tools address fragments of this problem:

The Missing Piece

What is absent from the landscape is a platform that treats the physical chemical container as the center of the compliance record. Such a platform must:

  1. Link each container to its regulatory paperwork (SDS, import approval).
  2. Make hazard data instantly accessible to the public via a durable, scannable code.
  3. Support offline warehouse inspections with automatic discrepancy detection.
  4. Provide separate, role-appropriate interfaces for regulators, importers, inspectors, and the public.

ChemTrace Pro closes this gap by combining an edge-hosted API, offline-capable mobile scanning, and static public hazard pages into one coherent, multi-actor system.


3. Solution Overview

Core Concept: One Container, One Identity

ChemTrace Pro applies a simple but powerful rule: every physical chemical container will receive a unique TagIdentifier (TID). That TID is encoded on an RFID chip and printed as a QR code on the container's label — a completely new process for the NMA. Scanning either identifier resolves to a single digital record containing:

This creates a closed loop: consignees declare imports digitally, NMA approves them digitally, the system generates RFID labels digitally (a new capability), and field inspectors verify physical inventory against the same digital record.

System Metaphor: A Digital Passport

ChemTrace Pro functions like a passport system for chemical containers. Each container gets a passport (the TID) that follows it from the port of entry to the warehouse shelf to the point of sale or use. Anyone with the right credentials — regulator, inspector, business user, or citizen — can scan the passport to see the container's history and hazard profile.

Key Design Principles

  1. Single source of truth. All five applications read from and write to the same database via a unified API. There are no ad-hoc data copies.
  2. Offline-first field operations. The Android scanner downloads inspection data before entering a warehouse and syncs results afterward. Connectivity is not required during the audit.
  3. Public safety by default. QR hazard pages are intentionally public and static. In an emergency, there is no login barrier between a first responder and the information they need.
  4. Domain-driven data model. Hazard classifications, UN numbers, container types, and approval states are first-class concepts, not afterthoughts.
  5. Edge deployment. The backend runs on Cloudflare Workers, and static hazard pages are served from Backblaze B2 via Cloudflare's CDN. This places data close to users in Suriname and keeps operational costs predictable.

The Five Applications

ApplicationUserTechnologyPurpose
NMA Staff Web AppNMA oversight staffVue 3, Vuetify, PiniaReview batches, manage companies, trigger label printing, view inspection reports
Consignee PortalImporting companiesVue 3, VuetifySubmit chemical data, upload SDS, track approval status
QR PWAFirst responders, publicNuxt 3 (static)Scan QR code to view hazard info, signal word, GHS pictograms, emergency contacts
Workers APIAll apps (backend)Hono, Cloudflare D1, Drizzle ORMBusiness logic, authentication, batch workflows, B2 uploads, inspection sync
RFID ScannerNMA field inspectorsKotlin, Jetpack Compose, RoomOffline warehouse scans, discrepancy detection, safety-status reporting

4. Architecture and Design

High-Level System Architecture

graph TB subgraph "User-Facing Applications" A[NMA Staff Web App<br/>Vue 3 + Vuetify] B[Consignee Portal<br/>Vue 3 + Vuetify] C[QR PWA<br/>Nuxt 3 static] D[RFID Scanner<br/>Kotlin Android] end E[Workers API<br/>Hono + Cloudflare Workers] A -->|HTTPS / cookie auth| E B -->|HTTPS / cookie auth| E D -->|HTTPS / JWT<br/>sync when online| E E --> F[Cloudflare D1<br/>SQLite database] E --> G[Backblaze B2<br/>S3-compatible storage] E --> H[BarTender Server<br/>RFID label printing] E --> I[Cloudflare KV<br/>session store] C -->|fetch static JSON| J[Cloudflare CDN] J --> G

Operational Flows

The following diagrams illustrate how the system operates across its multiple stakeholders. Each diagram is presented as a sequence flow with swimlanes to make actor responsibilities explicit.


Flow 1: Batch Rejection with Feedback and Resubmission

Not every import request is approved on the first submission. This flow shows how NMA staff communicate rejection reasons back to consignees, who can then edit and resubmit without losing their original draft.

sequenceDiagram actor Consignee actor NMAStaff as NMA Staff participant Portal as Consignee Portal participant Staff as Staff Web App participant API as Workers API participant D1 as Cloudflare D1 Consignee->>Portal: Submit batch for approval Portal->>API: POST /chemical_product_batches API->>D1: Insert batch (status = pending_approval) API-->>Portal: Submission confirmed Portal-->>Consignee: "Batch submitted for review" NMAStaff->>Staff: Review pending batch Staff->>API: GET /chemical_product_batches/{id} API->>D1: Fetch batch + chemical + SDS API-->>Staff: Full batch details Staff-->>NMAStaff: Display for review NMAStaff->>Staff: Reject batch with required feedback<br/>(e.g., "Missing UN number", "Incorrect hazard class") Staff->>API: PATCH /batches/{id}/reject API->>D1: Update status = rejected<br/>Store feedback + rejection_date API-->>Staff: Rejection recorded Staff-->>NMAStaff: Batch moved to rejected list API->>Portal: Push notification: batch rejected Portal-->>Consignee: "Batch rejected. See NMA feedback." Consignee->>Portal: View rejected batch + feedback Portal->>API: GET /chemical_product_batches/{id} API->>D1: Fetch batch + rejection feedback API-->>Portal: Rejected batch with NMA comments Portal-->>Consignee: Display feedback per field Consignee->>Portal: Edit batch (update hazard data,<br/>add missing UN number, revise quantities) Portal->>API: PATCH /batches/{id} API->>D1: Update batch data<br/>Clear rejection_date, clear feedback<br/>Status = pending_approval API-->>Portal: Batch updated and resubmitted Portal-->>Consignee: "Batch resubmitted for review" NMAStaff->>Staff: Review resubmitted batch Staff->>API: GET /chemical_product_batches/{id} API->>D1: Fetch updated batch API-->>Staff: Updated batch details Staff-->>NMAStaff: Display changes highlighted NMAStaff->>Staff: Approve batch Staff->>API: PATCH /batches/{id}/approve API->>D1: Update status = approved API-->>Staff: Approval confirmed Staff-->>NMAStaff: Batch approved API->>Portal: Push notification: batch approved Portal-->>Consignee: "Batch approved. Labels will be generated."

Key design points:


Flow 2: Import Request, Approval, and Label Generation

This is the central regulatory workflow. A consignee submits a chemical import request; NMA reviews and approves it; the system then generates RFID labels and public hazard data as an integrated, approval-gated step.

sequenceDiagram actor Consignee actor NMAStaff as NMA Staff participant Portal as Consignee Portal participant Staff as Staff Web App participant API as Workers API participant D1 as Cloudflare D1 participant B2 as Backblaze B2 participant BT as BarTender Server participant Printer as RFID Printer Consignee->>Portal: Create chemical product record<br/>Upload SDS PDF Consignee->>Portal: Create batch with inventory breakdown<br/>(supplier, quantities, expected arrival) Portal->>API: POST /chemical_product_batches API->>D1: Insert batch record<br/>Status = pending_approval API-->>Portal: Batch created, awaiting review Portal-->>Consignee: Confirmation + tracking ID NMAStaff->>Staff: View pending batches dashboard Staff->>API: GET /chemical_product_batches?status=pending API->>D1: Query pending batches API-->>Staff: Batch list with SDS preview Staff-->>NMAStaff: Display batch details NMAStaff->>Staff: Review SDS, validate hazard data<br/>Click Approve Staff->>API: PATCH /batches/{id}/approve API->>D1: Update status = approved<br/>Set approved_date API->>D1: Generate TagIdentifier (TID)<br/>for each physical unit API->>B2: Upload hazard JSON per TID<br/>/data/tID/{folder}/{tid}.json API->>BT: Submit print job<br/>(template, chemical name, TID list, quantities) BT->>Printer: Print labels with QR + RFID encode Printer-->>BT: Confirm print + TID→RFID mappings BT-->>API: Print completion report API->>D1: Store RFID mappings API-->>Staff: Approval + print confirmation Staff-->>NMAStaff: Show completion status API->>Portal: Push notification: labels ready Portal-->>Consignee: "Your batch is approved.<br/>Labels are ready for pickup."

Key design points in this flow:


Flow 3: Warehouse Inspection with Offline Scanning

This flow supports NMA field inspectors who audit warehouses, often in locations with poor or no mobile connectivity.

sequenceDiagram actor Inspector participant Scanner as RFID Scanner App participant Room as Local Room DB participant API as Workers API participant D1 as Cloudflare D1 rect rgb(230, 245, 255) Note over Inspector,API: PRE-INSPECTION (with connectivity) Inspector->>Scanner: Log in, select warehouse Scanner->>API: GET /inspections/download?warehouse=X API->>D1: Query expected TagIdentifiers<br/>for selected warehouse API-->>Scanner: Download TID list + chemical metadata Scanner->>Room: Store inspection data locally Scanner-->>Inspector: "Ready for offline inspection" end rect rgb(255, 248, 230) Note over Inspector,Room: FIELD INSPECTION (offline mode) loop For each container scanned Inspector->>Scanner: Scan RFID tag Scanner->>Room: Lookup TID in local DB Room-->>Scanner: Chemical name, batch, expected location Scanner-->>Inspector: Display chemical info Scanner-->>Inspector: Update running count<br/>"Scanned 45 / Expected 42 Mebrom 98" alt Discrepancy detected Scanner-->>Inspector: Highlight undeclared count (+3) end alt Container damaged Inspector->>Scanner: Mark unsafe + add photo/note Scanner->>Room: Record safety_status = unsafe end end end rect rgb(230, 255, 230) Note over Inspector,API: POST-INSPECTION (back online) Inspector->>Scanner: Tap Sync Scanner->>API: POST /inspections/{id}/sync<br/>(scan records + safety flags) API->>D1: Insert inspection_items<br/>Update tag safety status API->>D1: Generate discrepancy report API-->>Scanner: Sync confirmed Scanner-->>Inspector: "Inspection synced" end actor NMAStaff as NMA Staff participant Staff as Staff Web App NMAStaff->>Staff: View inspection reports Staff->>API: GET /inspections/{id}/report API->>D1: Aggregate scans vs expected inventory API-->>Staff: Report with:<br/>- total scanned<br/>- expected count<br/>- discrepancy<br/>- unsafe items Staff-->>NMAStaff: Display report

Key design points in this flow:


Flow 4: Public QR Scan at an Emergency Scene

This flow is optimized for speed and reliability. No login, no backend API call, no dynamic rendering.

sequenceDiagram actor Public as First Responder / Citizen participant Phone as Mobile Browser participant CF as Cloudflare CDN participant B2 as Backblaze B2 participant API as Workers API Public->>Phone: Scan QR code on container<br/>(qr.nma.sr/{tid}) Phone->>CF: Load PWA assets CF-->>Phone: Return cached Nuxt PWA Phone->>CF: Fetch /data/tID/{folder}/{tid}.json alt Cache hit CF-->>Phone: Return JSON from edge cache else Cache miss CF->>B2: Fetch JSON file B2-->>CF: Return hazard JSON CF-->>Phone: Return JSON + cache end Phone-->>Public: Display:<br/>- Chemical name + CAS<br/>- Signal word (Danger/Warning)<br/>- GHS pictograms<br/>- H-codes + P-codes<br/>- UN number + transport class<br/>- Supplier / consignee contacts<br/>- Link to SDS PDF alt Report incident Public->>Phone: Tap Report Phone->>Phone: Capture GPS location Phone->>API: POST /qr/tracking<br/>(location + message + tid) API->>D1: Store incident report API-->>Phone: Acknowledge Phone-->>Public: "Report submitted" end

Flow 5: Safety Incident Reporting from Field to Follow-Up

This flow shows how an inspector who discovers a damaged or leaking container triggers a safety alert that reaches NMA staff and the responsible consignee.

sequenceDiagram actor Inspector participant Scanner as RFID Scanner App participant Room as Local Room DB participant API as Workers API participant D1 as Cloudflare D1 actor NMAStaff as NMA Staff participant Staff as Staff Web App actor Consignee participant Portal as Consignee Portal rect rgb(255, 248, 230) Note over Inspector,Room: FIELD (offline mode) Inspector->>Scanner: Scan RFID tag on damaged container Scanner->>Room: Lookup TID Room-->>Scanner: Chemical info + expected location Scanner-->>Inspector: Display chemical details Inspector->>Scanner: Mark container as UNSAFE Scanner->>Scanner: Capture photo + note:<br/>"Dent visible, possible leak" Scanner->>Room: Store safety_status = unsafe<br/>with photo path and note Scanner-->>Inspector: Recorded locally end rect rgb(230, 255, 230) Note over Inspector,API: BACK ONLINE Inspector->>Scanner: End inspection + Sync Scanner->>API: POST /inspections/{id}/sync<br/>(all scans + safety flags) API->>D1: Insert inspection items<br/>Update tag safety_status = unsafe API->>D1: Flag container in batch record API-->>Scanner: Sync confirmed end NMAStaff->>Staff: View inspection report Staff->>API: GET /inspections/{id}/report API->>D1: Aggregate scans, discrepancies, safety flags API-->>Staff: Report with unsafe items highlighted Staff-->>NMAStaff: Display:<br/>- Container TID<br/>- Chemical name<br/>- Photo + inspector note<br/>- Warehouse location NMAStaff->>Staff: Review and confirm safety alert Staff->>API: POST /safety_alerts<br/>(batch_id, tid, severity, action_required) API->>D1: Create safety alert record API->>Portal: Push notification to consignee API-->>Staff: Alert dispatched Staff-->>NMAStaff: "Consignee notified" Consignee->>Portal: View safety alert Portal->>API: GET /safety_alerts?company_id=X API->>D1: Fetch alerts for consignee's company API-->>Portal: Active alerts with container details Portal-->>Consignee: Display:<br/>- Container ID<br/>- Inspector photo<br/>- Required action:<br/> "Remove from shelf,<br/> contact supplier for replacement" Consignee->>Portal: Mark action as completed<br/>(container removed, supplier contacted) Portal->>API: PATCH /safety_alerts/{id}/resolve API->>D1: Update alert status = resolved<br/>Log resolution_note API-->>Portal: Confirmed Portal-->>Consignee: "Alert resolved" API->>Staff: Push notification: alert resolved Staff-->>NMAStaff: "Consignee has addressed the safety issue"

Key design points:


Flow 6: User Onboarding and Granular Permission Assignment

This flow illustrates how NMA administrators create accounts for external company users and assign them fine-grained permissions within their organizations.

sequenceDiagram actor NMAAdmin as NMA Admin participant Staff as Staff Web App participant API as Workers API participant D1 as Cloudflare D1 actor NewUser as New External User participant Portal as Consignee Portal actor CompanyAdmin as Company Admin NMAAdmin->>Staff: Create new user<br/>(email, name, user_type = external) Staff->>API: POST /users API->>D1: Insert user record<br/>Generate UUID + temporary password API-->>Staff: User created Staff-->>NMAAdmin: "User created. Assign to company?" NMAAdmin->>Staff: Assign user to company<br/>(Warsha N.V.) with role = manager Staff->>API: POST /user_companies<br/>(user_id, company_id, role, permissions) API->>D1: Insert user_company join record API->>D1: Store permissions JSON:<br/>{can_create_batches: true,<br/> can_edit_batches: true,<br/> can_approve_batches: false,<br/> can_view_reports: true} API-->>Staff: Assignment saved Staff-->>NMAAdmin: "User assigned with manager role" API->>NewUser: Send welcome email<br/>(login URL + temporary password) NewUser->>Portal: Log in with email + temp password Portal->>API: POST /login API->>D1: Verify credentials API->>D1: Fetch user + company memberships API->>D1: Expand effectivePermissions<br/>(manager = explicit JSON) API-->>Portal: JWT token + user profile +<br/>company list + permissions Portal-->>NewUser: "Welcome. Please change your password." NewUser->>Portal: Change password Portal->>API: PATCH /users/me/password API->>D1: Update encrypted_password API-->>Portal: Password updated Portal-->>NewUser: Dashboard displayed Note over Portal: Dashboard shows only<br/>allowed actions based on permissions:<br/>- Create batch (enabled)<br/>- Approve batch (hidden)<br/>- View reports (enabled) CompanyAdmin->>Portal: Review team permissions Portal->>API: GET /user_companies?company_id=X API->>D1: Fetch all users for company API-->>Portal: User list with roles and permissions Portal-->>CompanyAdmin: Display permission matrix CompanyAdmin->>Portal: Update operator's permissions<br/>(add can_export_data) Portal->>API: PATCH /user_companies/{id} API->>D1: Update permissions JSON API-->>Portal: Saved Portal-->>CompanyAdmin: "Permissions updated" NewUser->>Portal: Refresh dashboard Portal->>API: GET /users/me<br/>(re-fetch permissions) API->>D1: Fetch updated effectivePermissions API-->>Portal: New permission set Portal-->>NewUser: Export button now visible

Flow 7: Chemical Spill Emergency Response and Origin Investigation

This flow demonstrates the system's most critical public-safety and regulatory-enforcement scenario: a chemical spill at an incident site. It shows how two different actors — a first responder and an NMA investigator — extract different but complementary information from the same container identifier.

sequenceDiagram actor Witness as Witness / Bystander actor Dispatcher as Emergency Dispatcher actor FirstResponder as First Responder participant Phone as Mobile Browser participant CF as Cloudflare CDN participant B2 as Backblaze B2 actor NMAInspector as NMA Inspector participant Scanner as RFID Scanner / Staff App participant API as Workers API participant D1 as Cloudflare D1 rect rgb(255, 240, 240) Note over Witness,Dispatcher: INCIDENT REPORTED Witness->>Dispatcher: "Chemical spill at warehouse on Main Street" Dispatcher->>FirstResponder: Dispatch fire + hazmat unit FirstResponder->>Phone: Arrive on scene, locate container end rect rgb(255, 248, 230) Note over FirstResponder,B2: PHASE 1: FIRST RESPONDER — HAZARD ASSESSMENT FirstResponder->>Phone: Scan QR code on spilled container<br/>(qr.nma.sr/{tid}) Phone->>CF: Load static PWA CF-->>Phone: Cached Nuxt app Phone->>CF: Fetch /data/tID/{folder}/{tid}.json CF->>B2: Get hazard JSON (cache miss) B2-->>CF: Return JSON CF-->>Phone: Hazard data Phone-->>FirstResponder: Display:<br/>- Chemical: Mebrom 98<br/>- CAS: 74-83-9<br/>- Signal word: DANGER<br/>- GHS pictograms: toxic, corrosive<br/>- H-codes: H300, H310, H330<br/>- P-codes: P260, P262, P264<br/>- UN: 2642, Class 6.1, PG I<br/>- Physical state: liquid<br/>- Flash point: none<br/>- Supplier: MEBROM LTD<br/>- Consignee: Warsha N.V. FirstResponder->>Phone: Tap "View SDS" Phone->>CF: Fetch SDS PDF from /sds/... CF->>B2: Get PDF B2-->>CF: Return PDF CF-->>Phone: SDS document Phone-->>FirstResponder: Full Safety Data Sheet<br/>(handling, PPE, first aid, spill cleanup) FirstResponder->>Phone: Tap "Emergency Contact" Phone-->>FirstResponder: Display:<br/>- Supplier: +852 3460 5520<br/>- Consignee: +597 4XXXXXX<br/>- Poison Control: +597 1XXXXXX FirstResponder->>Dispatcher: "Confirmed: toxic liquid, Class 6.1.<br/>Requesting Level B PPE and diking materials.<br/>Supplier contacted." end rect rgb(230, 245, 255) Note over NMAInspector,D1: PHASE 2: NMA INVESTIGATOR — ORIGIN TRACING NMAInspector->>Scanner: Arrive on scene NMAInspector->>Scanner: Scan QR code (or RFID if damaged) Scanner->>API: GET /tag_identifiers/{tid}<br/>(or lookup by RFID hex) API->>D1: Resolve TID → TagIdentifier → Batch → Chemical API->>D1: Fetch batch record:<br/>- batch_id<br/>- chemical_id<br/>- consignee_id<br/>- supplier snapshot<br/>- inventory line items<br/>- approved_date<br/>- storage_facility_id API->>D1: Fetch company record (importer/consignee)<br/>Fetch storage facility record API-->>Scanner: Complete origin profile: Scanner-->>NMAInspector: Display:<br/>- Chemical: Mebrom 98<br/>- Batch ID: 019c...<br/>- Imported by: Warsha N.V.<br/>- Approved: 2026-03-20<br/>- Declared inventory: 2040 cans × 454g<br/>- Assigned storage: Warehouse A, Shelf B-12<br/>- Supplier: MEBROM LTD, Hong Kong<br/>- Delivery date: 2026-03-27 end rect rgb(240, 255, 240) Note over NMAInspector,D1: PHASE 3: ROUTE INFERENCE FROM TRANSFER HISTORY NMAInspector->>Scanner: "How did this can get here?" NMAInspector->>Scanner: Query transfer history<br/>(same chemical + unit size) Scanner->>API: GET /chemical_movements?<br/>chemical_id=XXX&container_type=can&size=454&size_unit=g API->>D1: Query chemical_movements table<br/>Filter: chemical_id matches<br/>inventory item matches (can, 454g) API->>D1: Also query rfid_scans / location_reports<br/>for this TID (if any) API-->>Scanner: Transfer records found: Scanner-->>NMAInspector: Display transfer timeline:<br/>1. 2026-03-27: Delivered to Warsha N.V.<br/>&nbsp;&nbsp;&nbsp;(batch approved, system-generated labels issued)<br/>2. 2026-04-05: Transfer request #4412<br/>&nbsp;&nbsp;&nbsp;Warsha N.V. → RetailCo (200 cans × 454g)<br/>3. 2026-04-12: Transfer request #4489<br/>&nbsp;&nbsp;&nbsp;RetailCo → Warehouse B (50 cans × 454g)<br/>4. 2026-05-01: Transfer request #4501<br/>&nbsp;&nbsp;&nbsp;Warehouse B → Main Street Warehouse (12 cans × 454g) NMAInspector->>Scanner: Review transfer request #4501 Scanner->>API: GET /chemical_movements/{id} API->>D1: Fetch transfer details:<br/>- origin_company<br/>- destination_company<br/>- destination_address<br/>- requested_quantity<br/>- transport_method<br/>- driver_name (if recorded)<br/>- approval_status API-->>Scanner: Transfer #4501 details Scanner-->>NMAInspector: Display:<br/>- From: Warehouse B<br/>- To: Main Street Warehouse<br/>- Quantity: 12 cans × 454g<br/>- Transport: company truck<br/>- Driver: (not recorded)<br/>- Approved by: NMA staff on 2026-04-28 Note over NMAInspector: INFERENCE, NOT CONFIRMATION Scanner-->>NMAInspector: System note:<br/>"Route inferred from transfer requests.<br/>This container likely arrived via<br/>transfer #4501 based on matching<br/>chemical + unit size (can, 454g).<br/><br/>⚠️ Full RFID scan tracking at every<br/>transfer point is not yet enforced.<br/>Cannot confirm this specific can<br/>was on truck #4501 without<br/>intermediate RFID scan records." end rect rgb(255, 245, 230) Note over NMAInspector,D1: PHASE 4: INVESTIGATION DOCUMENTATION NMAInspector->>Scanner: Record spill findings Scanner->>API: POST /incident_reports<br/>(spill location, TID, estimated volume,<br/>photos, environmental conditions) API->>D1: Create incident report<br/>Link to batch, chemical, transfers API-->>Scanner: Report ID assigned Scanner-->>NMAInspector: "Incident IR-2026-0610-003 logged" NMAInspector->>Scanner: Flag batch for audit Scanner->>API: PATCH /batches/{id}<br/>context = incident_under_investigation API->>D1: Update batch context<br/>Create audit flag API-->>Scanner: Flagged Scanner-->>NMAInspector: "Batch flagged. NMA compliance<br/>team notified." API->>Staff: Push alert to NMA compliance desk Staff-->>NMAStaff: "Incident at Main Street Warehouse.<br/>Mebrom 98 spill. Batch under audit.<br/>Transfer #4501 requires verification." end

Key design points:


Flow 8: Chemical Product Lifecycle and Retest Management

Chemicals are not static records. They move through states from initial registration to approval, then through periodic retesting, and eventually to expiration or disposal. This flow shows how the system tracks that lifecycle.

sequenceDiagram actor Consignee participant Portal as Consignee Portal participant API as Workers API participant D1 as Cloudflare D1 actor NMAStaff as NMA Staff participant Staff as Staff Web App actor Inspector participant Scanner as RFID Scanner App rect rgb(240, 240, 255) Note over Consignee,D1: PHASE 1: REGISTRATION & APPROVAL Consignee->>Portal: Register new chemical product<br/>(name, CAS, physical state, SDS) Portal->>API: POST /chemical_products API->>D1: Insert chemical (status = pending_approval) API-->>Portal: Created Portal-->>Consignee: "Chemical awaiting NMA review" NMAStaff->>Staff: Review pending chemical Staff->>API: GET /chemical_products/{id} API->>D1: Fetch chemical + SDS metadata API-->>Staff: Chemical details Staff-->>NMAStaff: Display hazard data preview NMAStaff->>Staff: Approve chemical for import Staff->>API: PATCH /chemical_products/{id}/approve API->>D1: Update status = approved API-->>Staff: Approved Staff-->>NMAStaff: "Chemical approved" end rect rgb(255, 248, 240) Note over Consignee,D1: PHASE 2: IMPORT & LABELING Consignee->>Portal: Create batch for approved chemical Portal->>API: POST /chemical_product_batches API->>D1: Insert batch (status = pending_approval) NMAStaff->>Staff: Review and approve batch Staff->>API: PATCH /batches/{id}/approve API->>D1: Approve batch + generate TIDs API-->>Staff: Labels generated Staff-->>NMAStaff: "Batch approved, system labels generated" end rect rgb(240, 255, 240) Note over NMAStaff,D1: PHASE 3: RETEST SCHEDULING Note right of D1: retest_date approaches<br/>(e.g., 6 months before expiration) D1-->>API: Scheduled check triggers API->>Staff: Push notification:<br/>"Mebrom 98 retest due in 30 days" NMAStaff->>Staff: Schedule retest inspection Staff->>API: POST /inspections/schedule<br/>(chemical_id, retest_type, due_date) API->>D1: Create scheduled inspection API-->>Staff: Scheduled Staff-->>NMAStaff: "Retest assigned to inspector" end rect rgb(255, 255, 240) Note over Inspector,D1: PHASE 4: RETEST EXECUTION Inspector->>Scanner: Download retest assignment Scanner->>Scanner: Locate batch in warehouse<br/>Sample container for lab testing Inspector->>Scanner: Record sample taken<br/>(TID, sample_date, lab_destination) Scanner->>API: Sync retest data API->>D1: Update batch:<br/>retest_sampled = true<br/>sample_date = now API-->>Scanner: Confirmed NMAStaff->>Staff: Receive lab results Staff->>API: PATCH /batches/{id}/retest_result<br/>(pass/fail, lab_report_url) API->>D1: Update batch:<br/>retest_passed = true<br/>Extend retest_date + expiration_date API-->>Staff: Dates extended Staff-->>NMAStaff: "Chemical cleared for continued use" alt Retest failed NMAStaff->>Staff: Mark batch for disposal Staff->>API: PATCH /batches/{id}/quarantine API->>D1: Update status = quarantined<br/>disposal_required = true API->>Portal: Notify consignee API-->>Staff: Quarantined Staff-->>NMAStaff: "Batch flagged for disposal" Portal-->>Consignee: "Lab retest failed.<br/>Batch must be disposed of." end end rect rgb(255, 240, 240) Note over Consignee,D1: PHASE 5: EXPIRATION OR DISPOSAL Note right of D1: expiration_date reached API->>D1: Scheduled status update<br/>status = expired API->>Portal: Notify consignee:<br/>"Batch expired. Disposal required." Portal-->>Consignee: Display disposal instructions Consignee->>Portal: Confirm disposal completed<br/>(upload disposal certificate) Portal->>API: PATCH /batches/{id}/dispose API->>D1: Update status = disposed<br/>disposal_date = now<br/>disposal_certificate_url = ... API-->>Portal: Confirmed Portal-->>Consignee: "Disposal recorded" API->>Staff: Push notification:<br/>"Batch disposed by consignee" Staff-->>NMAStaff: "Disposal complete, audit trail closed" end

Key design points:

Key design points:

Key design points in this flow:


Data Model Overview

The database schema is organized around the container-as-identity concept. Key entities and their relationships:

erDiagram COMPANY ||--o{ CHEMICAL : creates COMPANY ||--o{ CHEMICAL_BATCH : consigns COMPANY ||--o{ ADDRESS : has COMPANY ||--o{ CONTACT : has CHEMICAL ||--o{ CHEMICAL_BATCH : "grouped into" CHEMICAL_BATCH ||--o{ TAG_IDENTIFIER : "generates" TAG_IDENTIFIER ||--o{ RFID_TAG : "encoded as" TAG_IDENTIFIER ||--o{ INSPECTION_ITEM : "scanned in" INSPECTION ||--o{ INSPECTION_ITEM : contains USER ||--o{ INSPECTION : conducts USER ||--o{ USER_COMPANY : belongs_to COMPANY ||--o{ USER_COMPANY : has_members

Notable patterns:


5. Implementation Approach

Technology Stack Summary

LayerChoiceRationale
BackendHono on Cloudflare WorkersLightweight, edge-distributed, native D1/KV/B2 integration
DatabaseCloudflare D1 (SQLite)Managed, replicated, zero configuration; fits expected scale
ORMDrizzle ORMType-safe, explicit schema, lightweight runtime
AuthJWT + Cloudflare KVStateless verification with server-side revocation support
Staff / PortalVue 3 + Vuetify 3 + PiniaMature, strongly typed, consistent Material Design
Public PWANuxt 3 (static)File-based routing, excellent PWA support, zero backend load
Mobile scannerKotlin + Jetpack Compose + RoomNative Android, offline persistence, modern UI toolkit
Object storageBackblaze B2 + Cloudflare CDNCost-effective S3-compatible storage with global edge caching
Shared codepnpm workspaceSingle source of truth for types, models, and API contracts

Runtime Constraints and Implications

Cloudflare Workers run in V8 isolates with no shared memory between requests. This architecture provides automatic horizontal scaling but imposes constraints:

For ChemTrace Pro's workload — REST API calls, small database transactions, and B2 file uploads — these constraints are acceptable. The heaviest operation, batch approval with label generation, is bounded by the number of physical units in a single batch and completes well within worker limits.

Testing Strategy


6. Evaluation and Results

Functional Validation

The backend integration test suite contains 30 tests across 7 test files covering the core API surface:

AreaTest Count
User creation and company association6
Company CRUD and activation5
Chemical batch creation and lifecycle12
End-to-end user scenarios2

All tests pass against the local Miniflare sandbox with a seeded D1 database.

Operational Targets

The project defines clear success metrics tied to real-world operations rather than synthetic benchmarks:

MetricTarget
Label printing setup time< 2 minutes, then automatic
QR scan to hazard info display< 3 seconds
Import request to approval< 48 hours for standard chemicals
SDS extraction accuracy> 95% for supported formats
System availability99.5% uptime
Inspection sync reliability100% data integrity after sync
Undeclared detection rate> 90% of physical inventory verified

7. Discussion

Key Trade-Offs

D1 SQLite vs. PostgreSQL. D1 was chosen for operational simplicity and edge distribution. For the expected scale — thousands to low millions of records — SQLite is sufficient. If the NMA later needs complex analytics, multi-region writes, or millions of concurrent connections, a migration to PostgreSQL would be warranted. The Drizzle ORM layer makes that migration feasible without rewriting business logic.

Cloudflare Workers vs. traditional servers. Workers eliminate server maintenance and scale automatically, but they impose constraints: no long-running processes, limited execution time per request, and a non-standard runtime. The team accepted these constraints because the workload maps cleanly onto the Workers model.

Static QR PWA vs. dynamic rendering. Serving QR pages as static JSON from B2 means the public-facing site scales infinitely without backend load, but it also means hazard pages are eventually consistent. If a batch is corrected after labels are generated, the static files must be regenerated and re-uploaded.

Offline-first Android vs. real-time sync. The scanner app stores data locally and syncs explicitly. This enables inspections in warehouses without connectivity but complicates conflict resolution if two inspectors modify the same tag. The current design avoids this by assigning inspections to a single inspector and a single warehouse at a time.

Known Limitations

When Not to Use This System

ChemTrace Pro is purpose-built for national-scale chemical import regulation with RFID/QR traceability. It is not a general warehouse management system, a customs/border control integration, or a chemical synthesis/manufacturing platform.


8. Future Work

Planned enhancements include:


9. Conclusion

ChemTrace Pro addresses a concrete regulatory gap by treating the chemical container — not the document, not the company — as the unit of compliance. By binding each container to a unique identifier, an approval record, a Safety Data Sheet, and a hazard profile, the system creates end-to-end traceability from import declaration to public safety disclosure.

The architecture reflects the domain: an edge-distributed backend connects role-specific frontends and an offline-capable Android scanner around a single database. Shared TypeScript types, Drizzle ORM, and workspace packages keep the five applications aligned as the platform evolves.

The system is designed for extension. New regulatory workflows — retest scheduling, disposal tracking, expanded analytics — can be added as new domain routes and schema tables without reworking the core identity model.


References

  1. ChemTrace Pro System Specification
  2. ChemTrace Pro README
  3. Introduction to ChemTrace Pro
  4. Cloudflare Workers Documentation — https://developers.cloudflare.com/workers/
  5. Cloudflare D1 Documentation — https://developers.cloudflare.com/d1/
  6. Drizzle ORM — https://orm.drizzle.team/
  7. Hono Web Framework — https://hono.dev/
  8. Vue.js 3 — https://vuejs.org/
  9. Nuxt 3 — https://nuxt.com/
  10. Globally Harmonized System (GHS) — United Nations, https://unece.org/ghs-pictograms
  11. Montreal Protocol — United Nations Environment Programme, https://ozone.unep.org/