← All work
2026

ugkit

Typed, zero-dependency libraries for Ugandan software: administrative divisions, phone numbers, tax bands, currency formatting. Reference data treated as versioned artefacts, not hand-edited code.

Role
Author and maintainer
Year
2026
Stack
TypeScriptnpm workspacesNode.jsGitHub Actions

Every Ugandan product ends up rebuilding the same things. A district dropdown. A phone number validator that knows MTN from Airtel by prefix. PAYE bands. NSSF contributions. Amount-in-words for a receipt. Each team writes its own version, gets a few districts wrong, hardcodes last year’s tax rates, and ships.

ugkit is that layer done once: small, static, boring national reference data and validation logic, versioned and shared.

Packages

@ugkit/locale is the first one, and the engine is complete. Districts, counties, sub-counties, and parishes ship today, sourced from the Uganda Bureau of Statistics parish-level dataset.

Planned and scoped in the roadmap: @ugkit/phone (parse, validate, normalise MSISDNs and detect network from prefix), @ugkit/tax (PAYE bands, NSSF, Local Service Tax, withholding tax as versioned rate tables with calculators on top), @ugkit/currency (UGX formatting and amount-in-words), @ugkit/lang (number-to-words in Ugandan languages, Luganda first, for receipts, USSD, and IVR), @ugkit/faker (format-valid fake Ugandan test data), @ugkit/uneb (index number validation and PLE/UCE/UACE grading), @ugkit/efris (types and QR parsing for URA e-invoicing), and sts-token, a parser for the 20-digit prepaid electricity tokens people call Yaka codes.

Design rules

Six rules apply to every package, and they are the actual point of the project.

Data is not code. Reference data ships as versioned CSV and JSON artefacts, compiled by a build pipeline into packed JSON.parse modules. Never hand-edited object literals in a .js file.

Flat arrays, not nested objects. Static trees are stored structure-of-arrays with parent indices and contiguous children, so a child lookup is an array slice rather than a filter over everything.

Pay for what you use. Subpath exports and lazy indices. Importing districts must never pull in parishes.

Dataset versioning is separate from code versioning. The dataset version lives in package.json under ugkit.datasetVersion and can bump without an API change, and the reverse.

Provenance is a feature. Every dataset release records its sources, retrieval dates, extraction method, and validation output in a PROVENANCE.md that ships with the data.

Zero runtime dependencies. Always.

Where the honesty lives

UBOS disseminates administrative units only down to parish level. So the villages level ships empty rather than being filled in from an unverifiable source, and it stays safely queryable, returning empty results by contract until that policy changes. The full sourcing investigation, including what could not be obtained and why, is committed alongside the data.

The repository runs CI across Node 18, 20, and 22, with engine-invariant tests, a dataset quality gate, and a full-scale synthetic benchmark as separate scripts. Nothing is published to npm yet; publishing starts with @ugkit/locale once the remaining dataset levels land.