---
title: "Tariff calculator: concept note"
kicker: "UNCTAD · Tariff calculator · Step 1 of 7 · concept note"
status: "Reviewed 29-09-2026 · every question answered · waiting for the final agreement"
updated: "29-09-2026"
toc: true
---

# Tariff calculator: concept note

<p class="lead">One calculator of import duties and taxes, which runs on its own or inside a portal such as eRegulations. It asks the customs service where the customs service can compute, and computes from the published tariff where it cannot.</p>

<div class="summary">

**In one minute**

- **What:** a public page where an importer picks a product, describes a shipment and reads every duty and tax, with the rate, base and legal source of each.
- **How:** one contract, two providers behind it. The ASYCUDA provider asks customs; the computing provider computes from tariff data we ingest.
- **Where:** one installation per country, at `calculator-bi.eregistrations.dev`, `calculator-rw.eregistrations.dev`, and so on (D26).
- **Proof:** pilot 1 computes Burundi and compares every tax with OBR's answer; pilot 2 does the same for Rwanda.
- **eRegulations v7:** untouched while we build; it embeds the calculator once pilot 1 passes (D19).

</div>

This note answers the request of 28-09-2026, which set three points: take the prototype page as the frontend; extract the ASYCUDA integration from eRegulations and rebuild it as the calculator's backend; make a calculator possible without ASYCUDA. Two agents reviewed it adversarially; their findings are in [the review](reviews.html). Words used in a sense of their own are in [the dictionary](../dictionary.html).

## 1 · What exists today

<p class="says">Two calculators already call OBR: the prototype and eRegulations v7. Neither can serve a country without ASYCUDA.</p>

**The prototype** (04-08 to 25-09-2026) is plain HTML and JavaScript for Burundi and Rwanda, behind a small relay that holds the customs key. Its value is what was measured: which fields OBR requires, which declaration types it really computes, the 21 defects found on 06-08-2026. Its debt: Rwanda is a patched copy of Burundi, and there is no store and no cache.

**eRegulations v7** carries its own port of the calculator since 04-09-2026. It is bound to eRegulations: its public site does not compile without its administration library.

<details>
<summary>v7's calculator in detail</summary>

- `TariffsBO`, 447 lines, talks to ASYCUDA's `/api/v1`: 3 commodity endpoints, 11 reference tables, the estimate, `api-id` and `api-key` headers, the language on every call.
- Reference tables are cached in a SQL Server table, `TariffsCache`, for 7 days by default, filled on the first visit.
- Its own public API, `/api/tariffs/*`, in eRegulations' shapes, behind a feature switch, limited to 120 requests per minute per address.
- A Razor page and 644 lines of jQuery, with `?embed=true`.
- Ported in Admin pull requests #25 and #26, Public #38 and #44.

</details>

**Other customs systems.** Of those checked on 29-09-2026, only ASYCUDA's tariff API computes an estimate on request. Korea's UNI-PASS publishes tariff lookups without one; TANCIS, ICUMS, Webb Fontaine, Kenya's iCMS, GAINDE, CrimsonLogic and SOGET publish no tariff API that could be found. Everywhere else, calculators ingest the tariff and compute.

## 2 · What the calculator is

<p class="says">A page that tells an importer what a shipment will pay, line by line, with the legal source of each line. Never a declaration, never an assessment.</p>

It answers in the country's currency and languages, runs as one installation per country (D20), and can be placed inside any portal with one line. Whatever it shows is an estimate; only customs' assessment of a lodged declaration is binding. It does not classify goods: the importer picks the commodity code.

<div class="rule">

**The rule inherited from the prototype.** When customs computes, the calculator shows customs' answer and corrects nothing. A defect found in the customs service is reported to it, with an example it can reproduce.

</div>

<details>
<summary>Also in scope, each owned by a module of step 2</summary>

- Languages: French and English; Kirundi off on the public page, as OBR asked; Swahili and Kinyarwanda when their authorities supply the texts (module 0).
- Accessibility to the template's bar (module 0).
- Printing the estimate and exporting it as CSV, as the prototype does (module 3).
- A health probe per provider, and usage counts kept across restarts (module 9).
- No personal data: shadow-mode logs hold shipment values and origins only, and are deleted after a set period (module 9).

</details>

## 3 · Two providers behind one contract

<p class="says">The page speaks one language, the calculator's own. Each provider is translated into it, and no provider is ever asked to change (D23).</p>

**The contract** is the calculator's API: product search, product details, reference tables, estimate. Amounts are exact decimals, each with its currency, and codes are the calculator's. Neither ASYCUDA's shapes nor v7's are kept as they are: one has a single currency per shipment where OBR asked for one per cost element, the other returns results that cannot hold 53 184 493,74 exactly.

**Each provider has an adapter** that translates, and nowhere else is a provider's vocabulary seen. Where a provider cannot take what the contract carries, its adapter converts with that provider's own published rates, and says so.

**Every estimate says** which provider answered, which tariff version it used, whether the figure is indicative, and how each line was reached.

| Provider | What it does | Its figures |
|---|---|---|
| **ASYCUDA** | Sends the shipment to the customs service and shows its answer. A port of v7's `TariffsBO` | Customs' own |
| **Computing** | Computes from a published tariff version: customs value, applicable measures, rates, each tax on its base | Always marked indicative |
| **rwandatrade.rw** | Asks Rwanda's trade portal, as the prototype does (D21) | The portal's |

**Each installation chooses its provider** on its administration page, not in its environment (D20). The template chooses in the environment, so this is code the calculator adds.

<div class="rule">

**When ASYCUDA does not answer**, the page says so and shows no figure of ours, unless the country allows it; Burundi does not (D11). **Exports** go through ASYCUDA only, once OBR computes them (D15). **A stand-in** that invents answers exists for demonstrations and tests only; a live installation refuses to start with one, after another country's data was once shown as Burundi's.

</div>

**Shadow mode** runs both providers for one country: the importer sees the authoritative answer, and every difference with the other is logged, tax by tax. For Burundi it uses v7's production settings (D24). One call from our server shows whether OBR already accepts its address; OBR is asked only if not. Production calls are real traffic, so the pilot's are few and spread out.

## 4 · Tariff data without ASYCUDA

<p class="says">Where no customs service computes, the tariff is stored as data, and the engine computes from it. Nothing a country changes is code.</p>

**The model is the EU's TARIC, simplified.** It is what the EU, UK and Swiss tariffs use; there is no formal standard.

| Piece | What it says |
|---|---|
| **Measure** | Which tax applies, to which codes, from which origins, under which procedure, between which dates, under which legal act |
| **Component** | The rate: a percentage, an amount per unit, or several joined by plus, minimum or maximum |
| **Condition** | A certificate or a threshold the measure depends on |
| **Tax type** | Its order, its base, its currency, its rounding: VAT, for example, on customs value plus duty plus excise |

**A country is data and parameters.** What it needs beyond them, such as a vehicle's cylinder capacity, is a declared rule with its own test, never a branch in a country file. The common external tariff differs by EAC member, through stays, remissions and COMESA preferences, so these are measures of the country that applies them.

**Tariffs arrive through connectors**, each producing a draft version dated from the day a law takes effect. Pilot 1 needs the first two.

1. A spreadsheet or CSV schedule, with a reader for rate texts such as "25% or USD 200/t, whichever is higher".
2. Manual entry, for levies and exchange rates that change by notice.
3. A PDF gazette, extracted and then checked line by line by the analyst.
4. An API, where one exists.

<div class="rule">

**A draft is published only when** every declarable code has a customs duty, the analyst has read its differences with the version in force, and the golden cases pass: every tax equal to OBR's after rounding (D12), except taxes named in the register of known OBR defects. At least twenty varied golden cases before step 7.2 (D25).

</div>

<details>
<summary>Data register, tax sheets and aggregators</summary>

- **Data register.** Each country lists its sources: the authority, the document or address, its format, how often it changes, the connector, who checks it. Exchange rates carry their own validity.
- **Tax sheets.** Step 4 gives one sheet per tax: base, currency, rounding, order, legal source, who updates it, and what the page says if an importer disputes the figure.
- **Aggregators.** ITC's Market Access Map, UNCTAD TRAINS and the WTO's data can seed a first version but never replace a national source. TRAINS stops at 2021 for Burundi, and none carries national levies.

</details>

## 5 · Embedding, and eRegulations

<p class="says">Any portal places the calculator with one line. eRegulations v7 becomes one of those portals.</p>

**Embedding** is an iframe of the country's embedded page, or one script tag that inserts it and adjusts its height. A host may choose the colours, the logo and the language; anything more is a country setting. Only the sites listed for the country may frame it (D9).

<details>
<summary>Six security requirements for modules 8 and 10</summary>

- `frame-ancestors` built from each country's list of allowed sites.
- CORS limited to the calculator's own pages and those sites; the prototype allowed every site.
- Height messages sent to the allowed site only, and checked on receipt.
- Rate limiting that trusts only our own proxy's address header; v7 trusts one any client can forge.
- Reference answers cached, so public traffic spends the customs quota only on estimates.
- The customs key on the server only, with its holder, its rotation, and what happens when customs locks it.

</details>

**eRegulations v7, in three moves:**

1. **While we build:** this project does not touch v7. It keeps serving Burundi and receives changes from other requests, OBR's of 24-09-2026 among them (D19).
2. **When pilot 1 passes:** a small v7 change lets `/Tariffs` show the embedded calculator when a setting names it, and today's page when it does not.
3. **Once a country has run on the embed for an agreed period:** retiring v7's own calculator is decided (D1).

## 6 · How the work runs

<p class="says">Seven steps, each through four gates. Nothing is drawn or built before a step's final agreement.</p>

The gates are: draft, first agreement, adversarial review by agents, final agreement with its date. Every role is the analyst's (D8).

| Step | Ends with |
|---|---|
| 1 · Concept note | This page, agreed |
| 2 · Short specifications | One page per module, at most 450 words |
| 3 · Mockups | Public page, embedded page, administration; clickable |
| 4 · Detailed specifications | Contract, engine rules, one sheet per tax, failure policy per provider |
| 5 · Detailed mockups | Every state: empty, error, customs down, shadow |
| 6 · Build phases | One branch and one pull request per phase, each ending with something to click |
| 7 · The tool | 7.1 ASYCUDA provider and the page · 7.2 engine and pilot 1 · 7.3 administration · 7.4 embedding and v7 · 7.5 pilot 2 |

**Step 2's eleven modules:** 0 public page · 1 product search · 2 estimate form · 3 result and its evidence · 4 ASYCUDA provider · 5 engine · 6 tariff data and ingestion · 7 administration · 8 embedding · 9 health and usage · 10 technical.

**The product starts from `vertical-base`**, UNCTAD's template for services built by agents (Next.js, Prisma, Postgres), brought in at step 7 (D6, D18). It sets aside what a public calculator does not use, applicant accounts and registration, and the calculator adds the tariff engine, the provider choice and a light page that can be framed.

<details>
<summary>What the template gives, and how its spec fits the seven steps</summary>

- **It gives:** an installer that can run twice, country and platform administration with sign-in, reference lists and parameters, an agent server (MCP), a one-command check gate, the integration contract under which each provider is an adapter, and the four doors that say where every value is changed.
- **Its spec:** its brief is this note; its sources, legal basis and lists are written in step 2; its screens in steps 3 and 5; its golden scenario in step 4. Its documents on parties, lifecycle, money and output are answered "none, because the calculator issues nothing".
- **Its check gate** is necessary and not enough: it proves neither a real integration, nor security, nor accessibility, nor that a person can use the page. Each phase of step 7 closes those four checks before its final agreement.

</details>

## 7 · Pilots

<p class="says">Each pilot answers one question: does our arithmetic match the customs service's, tax by tax?</p>

**Pilot 1, Burundi.** The EAC tariff, Burundi's stays and remissions and its national taxes are ingested with their legal articles, computed by the engine, and compared with OBR's own estimate: on stored golden cases, and in shadow mode. The measure of success is how many golden cases match OBR, out of how many.

**Pilot 2, Rwanda** (D13). The same common external tariff with its own stays, remissions and taxes: it proves a second country is data, not code. It is compared with rwandatrade.rw's answers (D21).

## 8 · OBR's requests of 24-09-2026

<p class="says">OBR's requests are done in v7 by other requests, and carried here as requirements (D19).</p>

| OBR asked for | Where it lands here |
|---|---|
| All the currencies its system handles | Module 2, reference lists |
| Internal freight added; external freight renamed and explained | Modules 2 and 5; internal freight is outside the customs value |
| One currency per cost element | The contract (§3), module 2 |
| Import and export tabs | Module 2; exports through ASYCUDA only (D15) |
| A table of preferential treatments, filtered by origin | Modules 2 and 6; preferences are measures |
| Kirundi off; labels checked; menu position | Module 0 |
| Hosting at OBR | UNCTAD runs the calculator for now, then decided with each authority (D22) |

## 9 · Risks

| Risk | What covers it |
|---|---|
| A computed figure taken for an assessment | Every result says "estimate", names its tariff version and the legal act of each line. When one first reaches the public is decided once the calculator is finished |
| Rates go stale: finance laws in July, notices any day (a levy changed in December 2025), weekly exchange rates | Versions dated from any day; exchange rates with their own validity; a warning past a threshold |
| National levies, stays and remissions missing or wrong | The pilots' main test; each carries its legal act; golden cases catch drift |
| OBR's test service silent since 04-09-2026 | Production settings (D24) and stored golden cases |
| Public traffic spends the customs quota | Rate limiting, cached reference answers, key custody (§5) |
| The template's method is a first draft | Its origin commit recorded; fixes pulled in with `template:pull` |
| The embedded page grows heavy | A size budget checked in the build |

## 10 · Decisions

<p class="says">Every question of this note is answered. One is deferred: when a figure we compute may reach the public, decided once the calculator is finished.</p>

| Question | Answer | |
|---|---|---|
| ?1 When ASYCUDA is silent, may we show our own figure? | Never, unless the country allows it; Burundi does not | D11 |
| ?2 How close must a golden case be? | Equal to OBR after rounding, tax by tax | D12 |
| ?3 Which country is pilot 2? | Rwanda | D13 |
| ?4, ?8 What does v7 receive while we build? | Nothing from this project; OBR's requests come through other requests | D14, D19 |
| ?5 Exports in the first release? | Through ASYCUDA only | D15 |
| ?6 The repository? | `unctad-ai/tariff-calculator-platform` | D16 |
| ?7 Which contract? | Our own; no provider asked to change | D23 |
| ?9 How is a provider chosen? | One installation per country, set on its administration page | D20 |
| ?10 Which OBR host for pilot 1? | Production, with v7's Burundi settings | D24 |
| ?11 Rwanda's source? | The rwandatrade.rw tariff API | D21 |
| ?12 Golden cases before step 7.2? | At least twenty; known OBR defects exempted by name | D25 |
| ?13 When may a computed figure reach the public? | Deferred until the calculator is finished | |
| ?14 Who runs it? | UNCTAD for now, then decided with each authority | D22 |
| ?15 Each country's address? | `calculator-<country code>.eregistrations.dev` | D26 |

<details class="sources">
<summary>Sources</summary>

- The prototype, `unctad-ai/tariff-calculator`: `README.md`, `server.js`, `docs/BURUNDI.md`, `docs/NUEVO-PAIS.md` and `docs/handover/` at commit `4f0f969`, read 28-09-2026; its history from 04-08-2026.
- The OBR–UNCTAD meeting of 24-09-2026: `docs/handover/2026-09-24-reunion-OBR-CNUCED.md`.
- eRegulations v7: `eRegulations-4.0-Admin` (`Business/Tariffs.cs`, `Model/TariffsModel.cs`, `Model/Country/TariffsCache.cs`, commits `7992fe5` and `e4fb388`), `eRegulations-4.0-Public` (`Api/Presentation/Controllers/TariffsController.cs`, `AppCode/TariffsRateLimiting.cs`, `Views/Tariffs/Index.cshtml`, `wwwroot/assets/js/views/tariffs.js`), `eRegulations-deploy` (`releases/7.x`, `instances/burundi/instance.yml`), local copies read 28-09-2026.
- `unctad-ai/vertical-base` as of 26-09-2026: `README.md`, `spec/README.md`, `docs/method/M1`, `M4` and `M5`, `docs/foundations.md`.
- Research of 29-09-2026 on customs systems and tariff data: UK Trade Tariff API and its archived duty calculator; EU TARIC; US HTS; Korea UNI-PASS open APIs; WITS/TRAINS; WTO tariff data; the EAC CET 2022 gazette; the WCO Data Model.
- The review of 29-09-2026: [its findings](reviews.html).
- Not seen: OBR's live service (its test host silent since 04-09-2026); v7 pull requests #120 to #128, merged after the local copy; ITC's Market Access Map, which refused access; any machine-readable EAC CET; the rwandatrade.rw API's terms; vertical-base running, which was read and not installed.

</details>
