PetroNet System¶
Mongolia's integrated fuel monitoring and management platform
PetroNet collects the import, storage, distribution and retail of petroleum products across Mongolia into one data flow, watches it in real time, and gives the regulator, the fuel companies and the driver the same numbers. It is built for the Mineral Resources and Petroleum Authority (MRPAM) and replaces the current mpetro system — see the system requirements.
PetroNet System is not one application — it is an ecosystem. A driver, a station, a fuel company and the regulator are doing four different jobs, and each has a platform built for that job. Underneath, all of them share one set of data, one identity and one audit trail.
Монгол
·
العربية
·
中文
·
English
·
Français
·
Русский
·
Español
Contents¶
- The problem
- What the platform does
- The ecosystem
- The design decision
- Chain of custody
- What already exists
- How it is built
- Repository layout
- Getting started
- Configuration
- Deployment
- Testing
- Security
- Documentation index
The problem¶
Mongolia has more than 200 fuel companies, over 110 storage depots and more than 1,500 filling stations. Their data lives in two systems that do not talk to each other, and most of it is typed in by hand, once or twice a week.
There is no single place that can answer, right now, how many litres of which grade are where. So national reserves are managed by estimate, and when supply tightens the only instruments left are blunt ones — odd-and-even plate days, a flat ₮50,000 cap per fill, queues.
That is not only a supply problem. It is an information problem, and information problems are the kind software can actually solve.
What the platform does¶
| Public page | ||
|---|---|---|
| 1 | Chain of custody — import contract → customs → laboratory → terminal → transport → station tank → nozzle → customer. Every litre is traceable back to the batch it came from. | /supply |
| 2 | Station POS — a driver layer that speaks to dispensers and tank gauges of any make, from a modern forecourt controller down to a pulse counter, and keeps selling when the network is gone. | /stations |
| 3 | Vouchers — entitlements minted only from fuel that has physically arrived, then allocated by proximity, need and time waited. | /vouchers |
| 4 | State oversight — stock, price, quality, tax and discrepancy on one board, over an audit trail that cannot be rewritten. | /oversight |
It runs in two modes on one set of rails. In a crisis it rations: limits and quotas change in minutes, priority classes keep their reserve, vouchers go out with a time window. The rest of the time it supervises: tax, price, quality and stock monitoring, automatic import-to-sale reconciliation, demand forecasting and strategic-reserve alerts.
The ecosystem¶
| Platform | For whom | What it carries |
|---|---|---|
| Citizen platform | Drivers and citizens | The nearest station, its grades, how full it is, the daily entitlement and the voucher. Identity comes from eID |
| Company portal | Fuel companies | Depot and station registry, tank balances, and the flow for filing and correcting each reporting period |
| Regulator's command centre | MRPAM and inspectors | The national daily aggregate, days of stock, coverage gaps, reconciliation discrepancies and alerts |
| Station POS and edge agent | Filling stations | Nozzles, tanks, shifts, payment and receipts, over a local database: the sale does not stop when the network does |
| Warehouse and analytics | Analysts and management | Fed daily from the operational systems, cleaned and modelled in business language, with BI and forecasting on top |
| Console and observability | Operators | Organisations, permissions and audit behind their own sign-in, beside metrics, alerts and backups |
Under all of them sit one Go binary, one database and one identity: an ecosystem here means many workspaces, not many servers. Outward it connects to customs, the state registry, e-Barimt, eID and the tank gauges in the field.
The design decision¶
A voucher is not a promise. It is a reserved litre.
A voucher comes into existence only after fuel has physically entered a station's tank and the automatic tank gauge has confirmed the level rise.
Two things follow. The system can never promise more than it holds, so the queue has nothing to form around. And a station that does not report its deliveries generates no vouchers, so nobody is sent to it — compliance is enforced by the design rather than by an inspector.
Chain of custody¶
| Stage | What is recorded | Where it comes from |
|---|---|---|
| Import | Contract, supplier, grade, tonnage, Incoterms, price, expected date | Importer portal / API |
| Border | Declaration number, HS code, duties, crossing point | Customs |
| Quality | Octane, density, sulphur, water, laboratory certificate | Accredited laboratory |
| Metrology | Observed litres, temperature, density → litres at 15 °C | ASTM D1250 / API MPMS 11.1 |
| Terminal | Tank, capacity, level, free capacity, strategic-reserve transfers | Tank gauges |
| Transport | Tanker, driver, loaded volume, destination, GPS track, e-seal | Carrier module |
| Station | Received volume, level rise, discrepancy, receiving staff | Tank gauge + confirmation |
| Nozzle | Totalizer, per-transaction litres and amount, shift readings | Forecourt controller |
| Customer | Entitlement, voucher, redemption, receipt, VAT | PetroNet + e-Barimt |
Each node reconciles against the one before it, so a discrepancy names when, where and in whose custody it appeared.
What already exists¶
Not a plan — a record of what is in the repository, with tests, run against a real PostgreSQL. The full list, and what comes next, is in the development plan.
- Registry of depots and stations, plate numbers, status, XYP verification
- Product reference with JODI classification, seven grades
- Regulator permissions, with a row-level policy behind them
- Policy as data — limits, tolerances, deadlines, changeable without a release
- Station technical inventory (classes A–D)
- Reporting periods, submissions, lines and conclusions
- Validation rules — balance, continuity, capacity, deviation, metrology
- 15 °C volume correction (ASTM D1250 / API MPMS 11.1)
- Report versioning and a hash chain over it
- Excel template export and import
- Review workflow — approve, return, four-eyes
- Movements, with plate number, closing state and discrepancy
- National daily aggregate, coverage, days of stock remaining
- Missing-data detection and a coverage-gap report
- Reconciliation ΔA–ΔE
- Seven reports as Excel and CSV, scheduled and emailed
- Open data: the daily national aggregate at
/api/v1/petro/public/daily - The company reporting screen and the regulator screen
How it is built¶
PetroNet is a Level 2 distribution of the
Gerege Nexus platform.
There is no core code in this repository — one line of go.mod is the whole of
it. What lives here is the fuel business logic (modules/petro/) and the map,
operator screens and public pages built for it (frontend/).
The module registers its own routes, menus, permissions and migrations through
the public pkg/nexus contract and compiles into a single Go binary. Identity,
tenancy, RBAC, SSO, reporting and the audit trail come from the platform and are
not rewritten here.
The deployment authenticates people itself: its own sign-in, its own OIDC issuer, its own database. Citizens are identified through eID Mongolia rather than a password this system would have to keep.
Repository layout¶
main.go Registers the petro module and starts the platform host
modules/petro/ The fuel module: registry, reports, oversight, vouchers
migrations/ This module's SQL, one history
cmd/petro-import/ Importer for the existing mpetro data
catalog/ App catalogue, manifests and version chronicle
frontend/ Next.js web client — public site, map, operator screens
deploy/ Dockerfile, compose stack, monitoring, backup scripts
nginx/ The six vhosts this deployment answers on
docs/ This documentation, in seven languages
Getting started¶
Prerequisites: Go 1.26+, Node.js 20+, PostgreSQL 16+ (or Docker).
# Everything at once
docker compose -f deploy/docker-compose.yml up -d
# Or the API on its own
go run .
# And the web client
cd frontend && npm ci && npm run dev
The web client answers on http://localhost:3000.
A deployment with no organisation yet sends every visitor to /setup. The token
that wizard asks for is written to the API log once, at boot:
Configuration¶
The complete list is in .env.example. The values that
decide how a deployment behaves:
| Variable | Description |
|---|---|
PUBLIC_ORIGIN |
Where this instance answers. Defines CORS, the OIDC issuer and the eID callback in one place |
PETRONET_POSTGRES_PASSWORD |
This stack's own database |
SSO_DEFAULT_CLIENT_SECRET |
The platform refuses to start in production without it |
BRAND_* |
The deployment's name, description, colours and icons |
SERVICE_URL_* |
The addresses of the console, warehouse, backups, monitoring and documentation. Only the ones set are drawn on the front page |
EID_RP_UUID / EID_RP_SECRET |
The eID relying-party pair. Without them eID sign-in is unavailable |
CONTROL_PLANE_HOST |
The hostname the operator console answers on, and nowhere else |
PROMETHEUS_URL |
Where the console reads platform health from |
Deployment¶
The production host carries /opt/petronet-v2/ — src/ (a git checkout of this
repository), .env (chmod 600) and brand/. Nobody runs git pull there: the
only path to production is git → CI → the self-hosted runner on the host:
commit → PR → main → CI (ci.yml) ✓ → Deploy (deploy.yml)
→ self-hosted runner: deploy/ci-deploy.sh <sha> # git reset --hard <sha>
→ ./deploy.sh # build, migrate, verify, rollback
deploy.sh builds both the backend and the web image from this repository, so
the API, the map and the operator screens always ship at one revision.
Six hostnames stand beside each other: the platform (petronet.mn), the
operator console (admin.), monitoring (monitor.), the warehouse map
(dwh.), this documentation (docs.) and the backup notes (backups.). What
each is, and the traps in the nginx configuration, are in
this deployment's document.
Testing¶
go vet ./... && go test -race ./... # Go: unit and PostgreSQL integration
cd frontend && npm test && npm run build
CI runs both on every push and pull request, and builds both Docker images.
Security¶
- Session tokens are random 256-bit values; only their SHA-256 digest is stored.
- Passwords are hashed with bcrypt, and sign-in attempts are rate limited.
- Tenant data is isolated by a database role, a tenant context and row-level security on the declared tables. The regulator's reach is a policy in SQL, not a check in a handler.
- Report versions are chained by hash, so an approved submission cannot be edited without the chain saying so.
- The operator console has its own identity, cookie, audit trail and database
role, and answers only on
CONTROL_PLANE_HOST.
Report vulnerabilities as described in SECURITY.md.
Documentation index¶
| Document | Description |
|---|---|
| Documentation hub | Every document and translation |
| System requirements | What the customer asked for |
| Development plan | What is built, what comes next, and the acceptance criteria |
| Benchmarks | How other countries solved this, and what failed |
| This deployment | Hostnames, ports, backups — only this host |
| Architecture | The planes, the schemas, how data is isolated |
| Writing a module | The pkg/nexus contract |
| Operations | Deployment, monitoring, backup and restore |
| Runbooks | When something breaks |
| Translation | The language policy and the generator |
| Contributing · Security · Code of conduct | Project conduct |
License¶
Copyright (c) 2026 Gerege Systems Development Team, Gerege Nomadica
Foundation. Distributed under the Apache 2.0 License — see
LICENSE.
Flag icons by Flaticon (attribution).