01
The problem
Remittances are ~20% of The Gambia's GDP. Every Gambian family has someone abroad sending money home. The Central Bank of The Gambia has internal forecasts of FX rates and remittance inflows; ordinary Gambians don't. There is no public-facing forecast of what the Dalasi will do next month, no dashboard showing diaspora corridors, no calculator for 'what will £100 be worth when I receive it'. I built the citizens' version: a public dashboard that shows what the Dalasi is doing, where remittances are coming from, and what the next six months look like, so a family deciding when to send or receive can plan instead of guess.
02
How it fits together
03
What I read before writing code
- Read the Central Bank of The Gambia's annual reports (2019-2024) to understand which currencies matter for the Dalasi's stability (USD, EUR, GBP, CHF, JPY) and how seasonality plays in (remittance peaks during Ramadan, Eid, school-year start in September).
- Read World Bank KNOMAD methodology on bilateral remittance estimation. The data is corridor-level: UK to GM, US to GM, ES to GM, DE to GM, and so on. Knowing the methodology was crucial because it's mostly imputed from migration stocks + sender-country incomes, not from actual transaction data.
- Read Prophet's Bayesian framework (Taylor and Letham 2017) while working out how to handle Ramadan and Eid, which drift 11 days a year against the Gregorian calendar and so are invisible to a month-of-year seasonal term. Compared with SARIMA: Prophet better at multi-period seasonality, SARIMA better at short cyclicality. Used both, ensemble.
- Reverse-engineered the Central Bank of The Gambia website's network behaviour by opening their FX rates page in Chrome devtools. Saw an undocumented JSON endpoint at `cbg.gm/ajax/indicative-exchange-rates/{CURRENCY}` returning 25 years of daily rates. Authoritative, free, no key required. Confirmed it worked across all major currencies before betting the project on it.
- Read GitHub Actions cron syntax + storage limits. Decided to refresh forecasts daily via Actions instead of Vercel cron, Vercel's Hobby cron wakes the function once per day at a non-customisable schedule. Actions gave me precise scheduling and a free workflow log.
04
What I couldn't do
- All data sources had to be free. No Bloomberg terminal, no paid APIs, no licensed datasets.
- Forecast accuracy had to be honest about uncertainty (intervals, not point estimates).
- Dashboard had to load in seconds on a Gambian 3G connection, most users are on Tecno/Infinix phones, not iPhones.
- Could not call any paid API at runtime (per-user cost would crush the project).
- Had to be visually trustworthy. Bad design + financial data = users assume the data is wrong.
05
The decisions that shaped it
Decision 01
Reverse-engineer the CBG's undocumented JSON endpoint as the primary data source.
The Central Bank publishes daily indicative rates as a webpage with no documented API. Opened the network tab and found `cbg.gm/ajax/indicative-exchange-rates/{CURRENCY}` returning 25 years of daily rates as JSON, no auth required. Official, authoritative, free. Found by curiosity, not documentation. Built the entire pipeline on it; cross-validated against `exchangerate.host` as a backup. The trick is being willing to look in places nobody put 'API' in the URL.
Decision 02
SARIMA only, and the holiday model is still unbuilt.
The plan was a Prophet and SARIMA ensemble: Prophet for the Ramadan and Eid remittance spikes, which drift 11 days a year against the Gregorian calendar and are therefore invisible to a month-of-year seasonal term, and SARIMA for the shorter cyclicality in the FX series. What ships is SARIMA(1,1,1)(1,1,1,12) alone. Prophet is named as the next step in the exploration notebooks and was never built, so the holiday model is the largest open item rather than a shipped feature.
Decision 03
Static export pre-rendered at build time, not server-rendered per request.
The data updates daily, not by user request. Pre-render once at build time with the latest forecast, ship as static HTML + JSON, host on Vercel free tier. Loads instantly anywhere, costs nothing per visitor. The build is triggered by the daily Actions job that refreshes the forecast.
Decision 04
Daily GitHub Actions refresh + auto-commit, not Vercel cron.
Vercel Hobby cron runs once per day but at a server-determined time. Actions cron runs at a precise schedule, has free unlimited minutes for public repos, and produces a visible workflow log. The Actions job: fetch CBG rates, then recompute Prophet/SARIMA, then commit `data/processed/*.csv`, then push, then Vercel auto-deploys the updated build. The ~25 commits visible in `git log` (`Refresh forecasts 2026-04-12`, `2026-04-13`, …) are this loop running on autopilot.
Decision 05
Bypass Next.js fetch cache + use browser-like headers when querying CBG.
First version had `/api/fx` returning year-2000 rates instead of latest. Spent half a day debugging, turned out Next.js's fetch cache was holding a stale CBG response from build time. Worse: CBG occasionally returned different data based on user-agent (presumably to discourage scraping). Fix was to set `cache: 'no-store'` AND send a normal browser User-Agent header. Two unrelated bugs on the same code path (commits 06a6d18, 4e2d1e2).
Decision 06
Plain-language interpretation alongside the chart.
A line chart with a forecast band is not enough for the average user. Added a plain-English interpretation paragraph: 'The Dalasi has weakened ~3% against the Pound this year. Forecasts suggest moderate stability through the next 6 months with a wider band around Eid. £100 today buys GMD 8,750, your model suggests it'll buy 8,400-9,100 next month.' The chart is for analysts; the prose is for everyone else (commit ac5e78f).
Decision 07
Live-status badge with CBG date stamp, polled every 15 minutes.
Users don't trust financial data without knowing when it last updated. Added a 'Live · CBG date 2026-05-08' badge that polls the API every 15 minutes. If the date is more than 24 hours stale, it visibly flags it. Honest provenance over fake real-time-ness (commit db5e7b1).
06
What broke and how I changed course
- First version was just FX. Added remittances as an afterthought. Realised they mattered MORE than FX for the typical Gambian family, most don't trade currencies, they receive transfers. Reframed the dashboard around remittances and made FX the supporting layer. Reframing what the product is about, not just what it shows, was the harder pivot.
- Used `exchangerate.host` as the primary FX source initially. Then discovered the CBG endpoint by accident while inspecting the bank's website. Switched primary to CBG (official) and kept exchangerate.host as a cross-validation backup. Sometimes you build the wrong thing first because the right thing wasn't visible yet.
- Original ETL pipeline had no cleaning layer, just fetched, plotted. Found that CBG had occasional duplicate rows from their backend, plus a methodology break around 2010 where the calculation changed and rates jumped 8% overnight. Wrote a cleaning script: dedupe, outlier removal (3-sigma), and a methodology-break correction factor for pre-2010 rates. The data looked sane after, the forecasts stopped having weird spikes (commit ba82a7e).
- Mobile UX broke because the currency selector pills wrapped onto a second line under the amount input on narrow screens, pushing content off-screen. Restructured the layout so currency pills sit ABOVE the amount field on mobile, side-by-side on desktop. Mobile-first means actually testing on a real phone, which I should have done sooner (commit 64e7bec).
07
What I didn't know, and how I learned
- I did not know how Prophet and SARIMA differed before this project. I read both papers and fitted SARIMA with walk-forward holdouts, but I never got Prophet running on the Dalasi series, so the comparison I set out to make is still unmade.
- I was new to GitHub Actions cron schedules. First version had the wrong cron syntax and the job ran every minute. Fortunately discovered before committing API-key usage to that.
- Time-series cross-validation is different from random-split CV. You can't shuffle time-ordered data without leaking the future into the training set. Read about walk-forward validation and rewrote my evaluation pipeline. Forecast metrics dropped from optimistic to realistic, which was the point.
- I underestimated how much pre-2010 Dalasi data was non-comparable to post-2010 data due to a methodology break. Adding the correction factor was a research task, not a coding task, I had to read the CBG's methodology footnotes to find the conversion ratio.
08
What shipped
- Live FX dashboard for Dalasi vs USD, EUR, GBP, CHF, JPY
- 25 years of daily CBG rates
- 6-month-horizon forecasts with confidence intervals (SARIMA, walk-forward validated against a random-walk baseline)
- Bilateral remittance corridor breakdown (UK, US, Spain and Germany to GM)
- '£100 next month' calculator for diaspora users
- 60-day daily forecasts + monthly sending calendar
- Plain-language interpretation paragraph
- Daily auto-refresh via GitHub Actions, deploys on commit
- Static export, instant load on 3G
09
What's next
I'd add a free SMS-out for non-smartphone users, text the dashboard a query like 'GBP forecast' and get the answer back. Most of the audience that would benefit most is not on smartphones. Would need a free SMS gateway (Africa's Talking has limited free credits) or a Telegram bot as a stopgap. Adding next.
What I learned
Open data exists in unlikely places. The CBG endpoint was sitting in the network tab the whole time. Curiosity outranks documentation. When publishing financial forecasts, honesty about uncertainty (intervals, model ensembles, plain-language interpretation, last-updated timestamps) is the difference between trustworthy data and fake-precise data.