| What language is it written in? | Python 3 (runs on 3.11 or newer). |
| What framework / web server? | FastAPI, served by Uvicorn behind the institution's own Nginx/TLS. Pages are server-rendered HTML — no heavyweight JavaScript framework to vet. |
| What third-party dependencies does it pull in? | Three: fastapi, uvicorn[standard], python-multipart. Everything else is the Python standard library (sqlite3, hashlib, secrets, urllib). No numpy/pandas, no GIS runtime, no ML framework. |
| Where is the data stored? | A single SQLite file on the host's local disk — default data/portfoliocheck.db beside the app, overridable with the PORTFOLIOCHECK_DB environment variable. No separate database server to provision, licence or patch. |
| Does any data leave the machine? | No, by default. The built-in analyser (the offline default) makes zero outbound calls; every external caller is opt-in and OFF; OFFLINE=1 hard-disables all of them. It can run fully air-gapped inside your perimeter. |
| Is there any cloud dependency? | None. No SaaS back-end, no multi-tenant cloud store, no external licence server. |
| What compute does it need? | One modest CPU-only Linux VM. No GPU. The 200-asset demo screens in well under a second on a single core. |
| How are users authenticated? | Passwords hashed with PBKDF2-HMAC-SHA256, server-side sessions, Secure/HttpOnly/SameSite cookies, login throttling. No public sign-up. |
| How is it monitored / is it alive? | GET /health returns status, version and the offline flag; the service is memory-capped and self-restarts under systemd. |
| How is it backed up? | Copy the one SQLite file. The deploy archive excludes the data directory, so a redeploy can never overwrite a saved book. |
How it runs on your estate: one request path, one process, one file — inside your perimeter — and the screening pipeline that turns a book into a supervisory return.
Processing pipeline — the same steps every screen follows, deterministic and stdlib-only:
/api/screen — no borrower PIIOne VM inside BoG, behind your reverse proxy and SSO. Banks submit their climate return; BoG aggregates, peer-benchmarks and runs the system-wide view. This is the SARB-style «constrained bottom-up» home for the tool.
Each bank runs its own copy; only screened / aggregated indicators flow to BoG. Raw borrower and loan-level data never leaves the bank — strongest fit for Act 843 and inter-institution confidentiality.
If your platform team standardises on OCI/Docker it packages cleanly — but the default «one Python process + one file» is less than a container and usually the lower-risk option for a first deployment.
On-premise (tiers A/B). For managed hosting (tier C) there is nothing to install
— we host it and provision your team a login. There is no public download or self-sign-up:
installs are provisioned. Full commands are in web/DEPLOY.md.
tar xzf portfoliocheck-<ver>.tgz -C /opt/portfoliocheck
python3 -m venv /opt/portfoliocheck/.venv
/opt/portfoliocheck/.venv/bin/pip install -r /opt/portfoliocheck/web/requirements.txt # fastapi, uvicorn, python-multipart
chown -R www-data:www-data /opt/portfoliochecksystemd unit
(User=www-data, memory-capped, port 8012) and an Nginx vhost proxying
127.0.0.1:8012 with a certificate (templates ship in infra/).PORTFOLIOCHECK_OFFLINE=1 for a hard
air-gap; or point PORTFOLIOCHECK_SAFEGROUND_URL / _FLOODGUARD_URL at
the on-box engines to add the live engine overlay. Every external caller stays OFF unless
explicitly enabled.cd /opt/portfoliocheck && .venv/bin/python manage.py adduser \
risk@yourbank.com 'strong-passphrase' --bank "Your Bank" --role makercurl -s http://127.0.0.1:8012/health # {"status":"ok", ... } curl -s http://127.0.0.1:8012/diag # migrations_ok:true, schema stamped
PORTFOLIOCHECK_PARAMS at a JSON parameter set; its name then appears on every report.Thereafter every update is applied with infra/update.sh (checksum →
back up code + DB → apply → health-gate → auto-rollback) — see Fleet operations below.
Bank of Ghana already runs ORASS (Vizor/Regnology) as its returns-submission
channel. ORASS has no climate module — that is the gap this tool fills. PortfolioCheck
is the analytics / computation layer, never a rival submission portal. A dedicated adapter
(connectors/orass.py) is the seam, switched by one environment variable
PORTFOLIOCHECK_ORASS_URL:
| Mode | Behaviour |
|---|---|
| Console-native flag unset — pilot default |
Banks file their climate return directly through the console upload path. No ORASS work needed to run a pilot today. |
| ORASS-routed flag set |
The identical return is pushed to / pulled from the ORASS API, so it flows through BoG's existing pipe. The adapter ships the interface today; the ORASS HTTP calls are wired at integration time against BoG's API contract, and the transport is fail-soft (a submission never breaks if ORASS is unreachable). |
POST /api/screen — screen one property at the point of application from a loan-origination system (JSON in, JSON out; coordinates travel in the body, not the query string).GET /health liveness/version probe for your monitoring; the ORASS adapter above for the supervisory return.infra/update.sh verifies the release checksum, backs up the current code and database, applies the release (never touching the data directory), restarts, health-checks and auto-rolls-back if the new version is not healthy — so a bad release can never leave the service down. Your IT runs it; we need no access to your box.GET /diag reports version, configuration flags and schema/migration health (no borrower data, no secrets); manage.py supportbundle writes the same as a PII-free file your operator can email us to diagnose an air-gapped install. We never see a book to support it.MAX_ROWS_SEED / MAX_ROWS_LIVE at the top of web/app.py) that protect the shared public demo; an on-premise operator raises the analyser cap to the size of their own book. The live-engine cap stays modest only because it calls the engines serially — the built-in analyser has no such limit.store.delete_portfolio), which removes the book and its quarter-over-quarter snapshots; the public demo and ephemeral uploads persist nothing. Because the store holds a pseudonymised asset id + location and no borrower PII, an Act 843 erasure request is satisfied at the bank's own system of record — deleting the corresponding portfolio here removes the screened copy.journalctl -u portfoliocheck), so any shipper (rsyslog, Filebeat, a Splunk forwarder) can forward them to your SIEM. Separately, a tamper-evident, append-only audit log — logins and failed logins, filed returns (with fingerprint), adaptation submissions, supervisory verdicts, account changes; each with who, when, target and source IP — lives in the same SQLite store and is viewable/filterable by a supervisor at /bog/audit.infra/update.sh uses. If you require redundancy, run active-passive behind your load balancer over replicated storage for that one file; screening is deterministic and idempotent, so a standby returns identical results.systemd service (User=www-data, memory-capped) behind your Nginx/TLS.OFFLINE=1 gives a hard air-gap with the built-in analyser fully intact.PORTFOLIOCHECK_DB in core/store.py, the ORASS seam in
connectors/orass.py, connector gating (*_ONLINE, OFFLINE) in
connectors/*.py, routes/headers in web/app.py, and fleet operations in
docs/FLEET.md + infra/update.sh + core/diagnostics.py. See the
Security & Data-Protection briefing for the full data-flow and controls.
Screening-grade and illustrative until calibrated on the institution's own loss data.